Skip to content

Repository layout

The repository has four top-level areas: the Python backend, the React console, trained models, and planning material. Tests, the docs source and the deployment files sit next to them.

interlock/
  backend/            Python package: simulator, policy, shield, judge, server, CLI, eval
  frontend/           Vite + React + TypeScript console, Playwright smoke test
  models/             DQN checkpoints (gitignored except README.md)
  tests/              pytest suite
  docs/               this site (MkDocs Material)
  internal/           planning history, the evaluation report and plots
  CLAUDE.md           working agreement for the repo: rules, conventions, commands
  README.md           overview, results, getting started, deployment
  pyproject.toml      uv project, dependency groups, ruff and pytest settings
  uv.lock             locked dependencies, CPU-only torch on Linux
  mkdocs.yml          docs navigation and theme
  Dockerfile          backend image for the always-on container
  fly.toml            Fly.io machine configuration
  .dockerignore       allowlist of what enters the image
  .github/workflows/  docs.yml publishes this site to GitHub Pages

backend/

Every module has a docstring that names its role. Flags for a subcommand are the parameters of that module's command, so python -m backend.<module> and interlock <command> stay equivalent.

File Role
env.py make_env() for highway-fast-v0 with the demo overrides, ACTION_NAMES and ACTION_INDEX derived from highway-env.
train.py DQN training recipe, greedy checkpoint evaluation, parse_seed_range. Behind interlock train.
policy.py Policy wraps the loaded DQN: Q-values and ranked_actions().
paths.py Repo-relative paths: models, logs, internal, the default checkpoint, the env file.
facts.py State plus one candidate action to JSON facts and plain sentences; the render snapshot.
constraints.py Constraint, Zone, PRESETS, infer_checker, ConstraintStore with the zone lifecycle.
jev_client.py build_question, cache_key, JevCache, LiveJudge, JevClient (shape B with cache), make_client, resolve_jev_mode.
shield.py Thresholds, Decision, decide, fallback_action, resolve. Pure functions.
checkers.py One closed-form checker per preset rule, label and label_all. Scoring only.
logger.py build_record, JsonlLogger, read_log, make_run_id, default_log_path.
episode.py EpisodeRunner, Escalation, the three built-in operators, StepResult, Totals.
run_episode.py Headless episode with per-step output. Behind interlock run.
server.py Session, create_app, session_from_env, replay helpers. The WebSocket server.
twin.py Finds the shield-off log for a seed and builds the twin message the console draws under the road view.
metrics.py episode_metrics, aggregate, collect_checks, reliability, calibration. Records in, numbers out.
evaluate.py run_config, build_report, the sweep. Behind interlock eval.
label.py Hand labels for rules without a checker, keyed by facts and rule text. Behind interlock label.
autolabel.py The fixed standard for the two fuzzy presets, applied to every state the report pools. Behind interlock autolabel.
baseline_llm.py Chat-LLM baseline: the judge's questions on logged checks, sent to Claude models for one structured JSON number. Behind interlock baseline.
plots.py Three matplotlib figures from the report JSON.
cli.py The interlock Typer app: every module's command under its subcommand name, plus the console subcommand that supervises uvicorn and Vite.
cli_support.py The Typer settings every app shares, the standalone runner behind each module's main, and repo_relative for help text.
spike/jev_spike.py The offline-checkable Jev probe behind interlock spike; writes raw requests under backend/logs/spike/.
.env.example Template for backend/.env, which holds TYPESAFE_API_KEY and is gitignored.
logs/ Runtime output, gitignored except labels/: runs/ (console and CLI episodes), eval/ (one directory per configuration), spike/, labels/ (one JSONL per fuzzy rule, committed so the fuzzy-rule calibration is reproducible), jev_cache.jsonl, baseline_cache.jsonl (chat-model replies).

frontend/

Path Role
src/main.tsx Mounts App in StrictMode.
src/app.tsx Layout, the global keyboard handler, the error toast, the console/metrics switch.
src/store.ts The single Zustand store with five slices and the selectors.
src/ws-client.ts The one WebSocket, reconnect with backoff, message to store mapping, send().
src/types.ts TypeScript mirror of the log record, the protocol and the evaluation report.
src/backend-url.ts httpUrl() and wsUrl() from VITE_BACKEND_URL, same-origin when unset.
src/format.ts Formatting helpers and the decision label, class and color maps.
src/styles.css Tailwind import, theme font, the shared panel, btn, chip and badge classes.
src/components/ status-strip, road-view, twin-strip, decision-strip, inspector-drawer, approval-queue, constraint-panel, metrics-view.
e2e/smoke.spec.ts Two Playwright tests against a mock backend on port 8001 and Vite on 5174.
vite.config.ts React and Tailwind plugins, the dev proxy to SHIELD_BACKEND.
playwright.config.ts Starts its own backend and Vite for the smoke test.
package.json pnpm scripts: dev, build, typecheck, preview, test:e2e.
.vercel/project.json Links the directory to the Vercel project interlock-console.

models/

File Role
dqn_highway.zip The checkpoint Policy.load() uses by default. Promoted by interlock train --promote.
dqn_highway_20k.zip, dqn_highway_100k.zip Named training runs, each with a .eval.json next to it.
README.md The only committed file in the directory.

tests/

One file per backend module plus the CLI and the spike's fact builders. The testing page maps each file to what it covers and explains the shared fixtures.

internal/

Planning history, the Jev spike output, research notes, and the evaluation outputs internal/eval/latest.json and internal/plots/*.png. The server reads latest.json for the metrics tab and the Docker image carries it. The rest is background for the project and is not part of this site.

What is gitignored

backend/.env, everything under backend/logs/ except .gitkeep, everything under models/ except its README, the Python and frontend build caches, frontend/node_modules/, frontend/dist/ and Playwright's result folders. The Docker build context is an allowlist in .dockerignore, so a new runtime file the server reads has to be added there and copied in the Dockerfile.