Agent docs

foundry-runner (the first module built against the contract)

> As of 2026-08-18 23:40 IST, runner step 1 in progress.

As of 2026-08-18 23:40 IST, runner step 1 in progress. Skeleton written by the brains lane so the hands lane fills the sections as the CLI runs; every number below marked MEASURED comes from a run, everything else is a claim to replace. Source: /Users/alcatraz627/Code/Versable/foundry-runner (working tree past d076078; the import fix and the /manifest, /guide work are uncommitted pending the owner's word).

1. What it is#

The V1 runner: services-api/app extracted into the foundry_runner package behind a payload port (step 0, dcdd0dd), the shared password replaced by a keys verifier (d076078), then step 1: GET /manifest generated from what the runner actually serves and validated against ../contracts/manifest.schema.json (0 errors), GET /guide rendered from the manifest and nothing else, both answering 503 UNAVAILABLE with a gaps list when the manifest would not conform. Python, FastAPI, runs locally with zero env; V1 co-hosts the four capabilities on this one deployment under the canon/14 exception (../adr/001 Consequences, ../patterns/00 V1 carve-out). Called by the console (versable-forge-v6) through ../contracts/module-surface.md routes only.

2. Shape of the runner#

Runner owns everything it serves; the payload owns capabilities. manifest.py and guide.py are assembly only (no FastAPI import) so they test with no third-party dependency; routes live in usage.py. surface.routes is enumerated off the live router at request time (walking FastAPI's included routers, which app.routes does not flatten), never hand-written. Config gained RUNNER_MODULE (default unconfigured-module, deliberately implausible), RUNNER_MODULE_VERSION, RUNNER_ENV, GIT_SHA, GIT_BRANCH, BUILT_AT, RUNNER_PAGE_SIZE_DEFAULT, RUNNER_PAGE_SIZE_MAX, every one with a default.

3. Concern by concern#

Filled by inheritance from services-api at extraction (step 0 lifted services-api/app wholesale), with the rows step 1 changed marked; every inherited row is a claim to re-verify against foundry_runner/, not a read.

#ConcernMechanismWhereVendor-specific?
1Runner/payload seaminherited from services-api at extraction (versable-runner.md row 1); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
2Caller identitylocal verifier only, fixed context caller_id: local, env local, all scopes (canon/02); the shared password remains beside it outside local, a lapse; keys and remote not yet implementedfoundry_runner/identity.py, d076078no
3Tenancyinherited from services-api at extraction (versable-runner.md row 3); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
4Roles / RBACinherited from services-api at extraction (versable-runner.md row 4); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
5Submit surfaceinherited from services-api at extraction (versable-runner.md row 5); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
6Job state ownershipinherited from services-api at extraction (versable-runner.md row 6); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
7Derived vs stored statusinherited from services-api at extraction (versable-runner.md row 7); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
8Dispatch / queueinherited from services-api at extraction (versable-runner.md row 8); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
9Concurrencyinherited from services-api at extraction (versable-runner.md row 9); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
10Retry / backoffinherited from services-api at extraction (versable-runner.md row 10); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
11Heartbeatinherited from services-api at extraction (versable-runner.md row 11); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
12Cancelinherited from services-api at extraction (versable-runner.md row 12); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
13Resume / checkpointinherited from services-api at extraction (versable-runner.md row 13); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
14Idempotencyinherited from services-api at extraction (versable-runner.md row 14); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
15Storage of inputsinherited from services-api at extraction (versable-runner.md row 15); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
16Storage of outcomesinherited from services-api at extraction (versable-runner.md row 16); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
17Results reportinginherited from services-api at extraction (versable-runner.md row 17); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
18Logs per job and iteminherited from services-api at extraction (versable-runner.md row 18); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
19Tracinginherited from services-api at extraction (versable-runner.md row 19); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
20Usage meteringinherited from services-api at extraction (versable-runner.md row 20); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
21Limits / quotasinherited from services-api at extraction (versable-runner.md row 21); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
22Cachinginherited from services-api at extraction (versable-runner.md row 22); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
23Rate limiting outboundinherited from services-api at extraction (versable-runner.md row 23); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
24Configinherited from services-api at extraction (versable-runner.md row 24); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
25Secretsinherited from services-api at extraction (versable-runner.md row 25); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
26Human in the loopinherited from services-api at extraction (versable-runner.md row 26); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
27Completion signallinginherited from services-api at extraction (versable-runner.md row 27); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
28Capability discoveryGET /manifest generated from the live router and validated against manifest.schema.json, ETag/If-None-Match, 503 with gaps when non-conforming; GET /guide rendered from the manifest only; /usage remains the hand-written quickstartfoundry_runner/manifest.py, guide.py, usage.pyno
29Versioninginherited from services-api at extraction (versable-runner.md row 29); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
30Health / readinessinherited from services-api at extraction (versable-runner.md row 30); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
31Provisioninginherited from services-api at extraction (versable-runner.md row 31); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
32Local devinherited from services-api at extraction (versable-runner.md row 32); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
33Output deliveryinherited from services-api at extraction (versable-runner.md row 33); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
34Data retentioninherited from services-api at extraction (versable-runner.md row 34); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
35Multiple versionsinherited from services-api at extraction (versable-runner.md row 35); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
36Conformanceinherited from services-api at extraction (versable-runner.md row 36); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
37Outputs and exportsinherited from services-api at extraction (versable-runner.md row 37); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner
38Data ownership splitinherited from services-api at extraction (versable-runner.md row 38); re-verify against foundry_runner/ before trusting(as versable-runner, pending re-read)as versable-runner

4. Runner vs payload#

The seam is the payload port (foundry_runner/payload.py); step 0 measured it as one call plus two discovery reads. /usage is still payload-authored through describe() and stays as a hand-written quickstart beside the generated /guide (BACKLOG, retire when the console renders the guide).

5. Deliberate decisions#

  • /manifest returns 503 with gaps rather than a non-conforming 200 (now contract, ../contracts/module-surface.md).
  • Routes enumerated live, not listed, so the manifest cannot drift from the router.
  • Assembly modules kept dependency-free for testability.

6. Lapses#

  • Could not import between dcdd0dd and the step-1 fix (stale from app import lines, two of them lazy inside functions): step 0's tests exercised only the seam, so a green suite shipped a runner that could not start. The [declared-ready] shape; recorded so the pattern is remembered.
  • One verifier of four; conforming in env: local only until keys or remote lands.
  • A1, fixed. /health returned {"ok": true} where conformance.md A1 pins {status: "ok"}, inherited from services-api by the extraction. foundry_runner/main.py. No sha: the fix is uncommitted, see below.
  • A6, fixed. /build-info returned 404; the runner never served it. Added open, reading the same settings the manifest uses so the two cannot disagree. foundry_runner/usage.py. No sha, same reason.
  • Both fixes, and all of step 1, are uncommitted working-tree state as of this run. The owner has not asked for a commit and the agent does not commit unasked, so this doc cites files rather than shas. Every row above was measured against that working tree, not against d076078.
  • client_job_id and the jobs-index adapter are not implemented; both are step-1 pieces still open, so C3 (repeated client_job_id is idempotent) cannot pass whatever the run says.

7. Conformance, MEASURED#

runrows attemptedpassfailn/radvisorybyhow
2026-08-1840120271gcp-opusfoundry-conform http://127.0.0.1:6217 against the local runner, env: local, one synthetic payload declaring content.generate, --allow-writes OFF
2026-08-19 15:55 IST40122251gcp-fablefoundry-conform http://127.0.0.1:5110 --table conformance.md against the pm2 service after the full step-1 surface landed (contract create, outcomes, jobs index, facets, re-run); the two FAILS are B1 and B2, /jobs and /stats answering 200 to an anonymous caller: the service is deliberately open (no app password, local verifier only), so these are honest fails by configuration where the earlier run read them as n/r on a 5xx; 17 rows still unattempted by the CLI (the same set); --allow-writes adds no rows yet because the mutating checks are not written, only gated

17 further rows the CLI does not attempt at all: B4, B5, B6, B7, B8, C1, C2, C4, D1, D3, D4, D5, D6, D7, D8, D9, E4. They have no check written, so they are neither passes nor failures. The CLI names them on every run rather than omitting them, because a row absent from a report reads as a row that passed.

Zero failures is not a clean bill. It means every row this configuration could reach passed. The rows most likely to fail are among the 43 that did not: tenancy, per-caller credentials, callbacks, ceilings. --allow-writes was off, so nothing that creates or mutates a job ran (C3, C5, C6, E1, E2, H3).

Three rows ran here for the first time in the estate: H4, H5 and H6, the membership checks manifest.md § Validation assigns to conformance rather than to the schema. They were n/r everywhere for one reason, that nothing served /manifest, so there was no declaration to read back.

This measures a local process with no queue, no real storage and no credential. It says nothing about a deployed foundry-runner, and there is not one yet. Evidence: ../evidence/20260818-foundry-runner-conformance.md.

Section H (manifest against behaviour) reported beside the contract rows; group M (module-supplied evals) reported separately and never counted.

8. Unproven#

Everything in sections 3 and 6 not tagged with a file:line; the tally until the row above is filled.

@versable-git/ui · reference, canon, and method, read in place