Jessie James

case study · 01

cpractice

An AI-assisted C practice platform. The model explains; a validated test suite decides.

Python · FastAPI · SQLite · pytest · plain HTML/CSS/JS frontend. Started 10 Oct 2026.

01 — the problem

where feedback goes wrong

You want to practise C, and you want to know whether your program is actually right. The obvious way to get that feedback is to ask a model — and that is exactly where it goes wrong.

A model will tell you the code looks correct. It will also tell you the code that segfaults looks correct, because it is pattern-matching your text rather than running your program. It is a confident reader, not a referee.

So the question is not "can a model grade C". It is where do you put the model — and what is allowed to decide the verdict.

02 — the principle

design decision #3
AI generates and explains.
Deterministic software grades.

The plan puts it as a hard rule: the AI never decides pass/fail. The model can write the problem, hint you when you are stuck, and explain a failure — but the verdict comes from compiling your code and comparing its output against a validated test case.

03 — the grading loop

no model in the path
  1. You write the solution In your own editor, in plain C. No in-page IDE, no scaffolding that quietly fixes your braces.
  2. It is compiled with a pinned flag set -std=c11 -O2 -pipe -Wall -Wextra — the same flags every time, so a verdict is reproducible.
  3. It runs in a throwaway workspace A dedicated temp directory, a wall-clock timeout, a memory cap and an output limit. A runaway loop gets killed, not the machine.
  4. Output is compared, not judged stdout is checked against the expected output of a declarative test case. Same input, same bytes, same verdict.
  5. You get a result and a diff Pass or fail, plus the failing case and what came out instead. That is the part that teaches you something.
  6. Only then does the model help Progressive hints and an explanation of the failure. It has no vote on whether the answer was right.

04 — the trust boundary

two different jobs
untrusted · the model

Writes, explains, nudges

Generates candidate exercises behind a quality gate, explains a failing test, hands out progressive hints. Fast, useful, and occasionally wrong — which is fine, because it never signs the verdict.

trusted · the grader

Compiles, runs, compares

Pinned compiler flags, enforced time and output limits, byte-level comparison against expected output, deterministic pass/fail. Provider-independent — swap the model, the grading does not change.

One decision makes the rest work: tests are declarative data, not arbitrary AI-generated scripts. The model may propose a problem, but it cannot author its own test that trivially passes. The test cases live in versioned problem packages, and they are validated independently of whatever produced the exercise.

05 — the hard parts

found by experiment

Most of the work was not the AI part. Building a sandbox that behaves the same on a phone, in a container, and on a laptop meant documenting a list of platform findings instead of guessing. These are the ones that changed the design:

findingwhy it hurtswhat I did
shared storage is noexec binaries compile but cannot run on Android shared storage repo and grading workspaces live in an exec-capable filesystem
RLIMIT_AS aborts the bionic linker the program dies at load, intermittently, at every limit skip RLIMIT_AS on Android
RLIMIT_NPROC corrupts signal reporting crashes report as exit 1, which reads as a wrong answer skip it on Android; detect crashes via negative exit codes
no Docker, no root locally strong sandboxing is not available on a phone isolation = dedicated tmpdir + timeouts + output caps; containers when hosted
Termux gcc is clang 21.1.8 some flags behave differently pinned flag set; no -fsanitize, no -march=native
no Android wheels for pydantic-core pip compiles Rust from source, 20–40 minutes pay it once; wheels are cached afterwards

One more trap, this time in the devcontainer: adding any ghcr.io feature makes Codespaces fail the build and silently fall back to a minimal recovery container — no Python, no secrets, no setup script. It looks like a working codespace and is not. The fix is written down in the plan so nobody "improves" it back into a broken state.

06 — the stack

and how it is tested
python fastapi pydantic sqlite pytest gcc html css javascript codespaces devcontainer

The platform has its own pytest suite, and it runs with mocked AI provider responses — testing CPractice never needs a live API key. The frontend is plain HTML, CSS and JavaScript with no framework and no CDN, which keeps the same constraints I use here: nothing to install, nothing to trust at runtime.

07 — status

milestones 1–5

The build order is strict: foundation → grading engine → problem-package contract → AI generation → learning feedback. The deterministic checker is proven before any AI generation is added, so the part that decides correctness never depends on the part that cannot be trusted.

That sequencing is the whole project in one sentence. The rest is plumbing, and plumbing is fixable.

say hello

let’s build something.

For work, collabs, or just to say hi — my inbox is open.

Bukidnon, PH github ↗ email ↗