Agent docs

Building a module

What a request for a new module brings along, in the order that keeps the build from debugging its…

What a request for a new module brings along, in the order that keeps the build from debugging its own seams. Written for the three modules the owner named as immediate deliverables (image generation, part-type matching, attribute normalization), each of which exists in some form in enhancement-product and speedway and now becomes a standalone module. The skeleton is Anh-Tuan's onboarding checklist (snapshot, method, registry) with the "one runner deployment" framing removed and the contract's concerns put around it.

Audience: an agent or a person about to start a module.

Before the first line#

  1. Name the capability and its variants. One or more ids (parttype.match, judge.parttype), what each requires, what each provides, from the field registry (../contracts/manifest.md). If a field is missing from the registry, that is the first PR.
  2. Write the manifest first. Params, item, output schemas; error types; review reasons; confidence source; ceilings; meters. It is the spec the runner validates against and the UI is generated from, and writing it before code is what stops the schema being whatever the first implementation happened to accept.
  3. Decide the reference data. What snapshot the payload loads (a taxonomy, PCdb, accepted values), where it comes from today (App V5's Mongo or Pinecone), how big it is, and whether it bakes into the image or downloads at boot (../canon/05-storage-and-persistence.md). The snapshot tool is written once for all modules; if it does not exist yet, this module writes it.
  4. Decide the topology and the queue adapter. Self-push on Cloud Tasks is the default on GCP; api/worker split when the payload's memory or duration profile says so (../canon/12-deployment-and-environments.md). In-process is always supported.
  5. Pick the state storage. Object-per-outcome with derived state unless the module needs to query outcomes by a non-key field (../canon/05, "The state-table decision").
  6. Check what exists. The three instances have already solved most of this concern by concern (../canon/00-overview.md, the matrix). Copy the winner named there rather than the nearest file.

The build order#

runner template ──► payload entry point ──► manifest served ──► local mode green
│ │
▼ ▼
in-process conformance run ◄───────────────────────── dev deploy, identity, queue
instance breakdown written ──► lapses filed against canon
  1. Start from foundry-module-template (one example capability on the runner, ../plans/runner-template.md step 1b) for a new module; for one of the four V1 capabilities, add it to the co-hosted foundry-runner manifest under the V1 carve-out below. Only when neither exists yet: versable-runner's app/ with the upgrade checklist in ../contracts/module-surface.md applied (tenant, client_job_id, /outcomes, /review, /manifest, /build-info, callbacks, verifier port). Do not start from a payload file.
  2. Write the payload behind the seam. One entry point per capability, receiving an item and a context, returning an outcome, using only the runner's verbs: the checkpointed iterator, the poll primitive, the cancel signal, meter, log, cached, trace (../canon/01-runner-and-payload.md). If the payload wants something not in that list, stop and add it to the runner, then use it.
  3. Serve the manifest from the same object the validator reads.
  4. Local mode green first. env: local, zero env, in-process queue, local storage, local verifier; submit a five-item job, read outcomes, cancel a job, read the per-item log (../canon/13).
  5. Run the conformance table in-process (../contracts/conformance.md). Every row that fails is a runner bug or a manifest lie; fix before deploy.
  6. Dev deploy with its own identity, bucket, queue, secrets by reference, /build-info stamped, an issuer client for the module in dev (../canon/02, ../canon/12; 02-deploying-a-module.md). Re-run conformance over the wire.
  7. Write the instance breakdown (../instances/README.md template) before calling it done, Lapses included. A module that ships without one is invisible to the next builder.
  8. Prod only after dev has run a real job for a real caller.

Where the five named modules already are#

ModuleExists asWhat is missing to be a module
content generationservices-api on Cloud Run, the most complete instancetenant, caller identity, client_job_id, /outcomes and /review, manifest, build-info, callbacks, per-item log, retention; needs_review instead of RESEARCH_QUALITY_LOW errors; move Langfuse behind the runner's verb; separate dev identity
image generationa method in App V5 lib/pipeline/methods/image_gen.py; blocked on S3 output, Oxylabs image search, and an api.auth owner lookup import (Von's roadmap)the import-closure cut services-api already did once; an artifact store on the module's own cloud (canon/05); Oxylabs behind the vendor cache and rate limit
part-type matchingspeedway partType.server.ts (Gemini, Firestore); App V5 auto_match_part_type and agents; services-api Phase 2 with a baked taxonomy snapshota taxonomy snapshot per tenant vocabulary; judge.parttype as a variant; the field registry entries part.type, part.type_confidence
attribute normalizationspeedway normalize.server.ts; App V5 attribute_search_agent + attribute_aggregator; services-api Phase 2 with PCdb SQLiteaccepted-values snapshot per tenant; attributes.normalized and attributes.evidence in the registry; review reasons for net-new values
data extractionextraction-api + extraction-worker, GCP project versable-infra, the most mature deployment (39 revisions, pgbouncer sidecar, VPC); source location being confirmedenvironment discrimination (it knows one), callbacks instead of polling, a manifest, tenant on jobs; everything else it may already have, pending the source

V1 carve-out (2026-08-18, expires when the first module ships standalone)#

The four V1 capabilities are built co-hosted on one runner in three days (../plans/v6-decision-set.md D4, D8, D11), which is inside the do-nots below only with these stand-ins, each named so the pattern keeps its authority for module five:

  • "manifest before code": the manifest is written the same day as the payload's first cut, not first; check-examples.py and the generated schema gate it before the payload lands.
  • "in-process conformance run green before deploy": the rows the CLI can run by hand are run and recorded with the tally MEASURED; the rest are named not-runnable in the instance doc.
  • "instance breakdown before done": one instance doc for the co-hosted deployment, instances/foundry-runner.md, with a per-capability section, rather than four.
  • "snapshot, never live Mongo or Pinecone": stands as written.

Do-nots#

  • Do not start from a payload file and grow a runner around it.
  • Do not write code before the manifest.
  • Do not open a live connection to App V5's Mongo or Pinecone; snapshot.
  • Do not deploy before local mode and the in-process conformance run are green.
  • Do not deploy dev under prod's identity.
  • Do not call it done without an instance breakdown.
@versable-git/ui · reference, canon, and method, read in place