Skip to content

Code generation

Several files in the repository are derived from the code: the SDKs' operation tables and wire types, the reference pages, the Codex plugin's copies, the op reach manifest, the schema dump. Each is committed, so a reader sees it without running anything, and the generators marked below have a --check mode that exits 1 when the committed file differs from what it would write. make lint runs each of those checks, and CI runs them in the lint and sdk-typescript jobs and the unit suite, so a stale file fails with the command to run named in the failure.

Why commit generated files. A generated file that is only built on demand is invisible in review: a change to an operation's input model would change the SDK types without a diff anyone reads. Committing them puts the effect of a change next to the change, and the --check gate stops the two drifting apart. The cost is a regeneration step in the change that moves the source.

The generators

All live in scripts/codegen/ and share scripts/codegen/common.py (the repository root, the facts an SDK table carries per op, ruff formatting, an atomic write, and the write-or-check protocol).

Script Writes From --check in make lint
sdk_py_gen.py sdk/python/loom/ops.py: OpName, the OPS table (idempotency, role, maturity, latency budget) and one TypedDict per input and output model server.operations.registry.ALL_OPERATIONS yes
sdk_py_client_gen.py sdk/python/loom/client.py: OpsClient, one method per op (agent.client.tracker.source_add), typed with the ops.py names server.operations.registry.ALL_OPERATIONS yes
sdk_py_admin_gen.py sdk/python/loom/admin_routes.py: AdminRoutes, one method per /v1/admin/* route (agent.deployment.routes.settings_set) the /v1/admin/* routers: each route's method, path, gate and docstring yes
sdk_ts_gen.py sdk/typescript/src/generated/ops.ts: one interface per model, the OPS table, the OpMap type server.operations.registry.ALL_OPERATIONS (the models' JSON Schemas) yes
agent_plugin_sync.py the Codex plugin's copies of the hook scripts (hooks/*.mjs), the shared library (lib/*.mjs), the skills and the listing icon (assets/), byte-identical with the executable bit integrations/claude-code-plugin/ yes
settings_reference.py the generated configuration reference server/settings/catalog.py: each field's env var, type, default, and the comment above it yes
settings_schema.py schemas/loom-settings.schema.json, the JSON Schema of the YAML settings file the settings fields' types, bounds, defaults and comments, secrets left out yes
settings_example.py loom.example.yaml, the settings-file template: every setting but the secrets, commented out at its default under its description, by area the settings catalog's fields, defaults, comments and live marks yes
docs_reference.py the generated regions of docs/reference/: operations, workers, cli, errors, schema, status_ops, status_switches, admin_routes (the operator-route table) the registry, server/workers/registry.py (_WORKERS), tests/unit/snapshots/cli_tree.json, server/core/errors.py, schemas/schema.sql, server/settings/catalog.py, the /v1/admin/* routers yes
op_reach_census.py server/operations/reach_manifest.py (--write-manifest); a census table otherwise the registry and the MCP toolsets yes
wire_changelog.py nothing; prints what changed for clients between two revisions the capability catalogue built at each revision no
gen_py_library_methods.py server/code/domain/extract/py_library_methods.py the running interpreter's standard library no (interpreter-dependent)
push_langfuse_prompts.py server/observability/langfuse_prompts.lock.json (--write) the prompt constants in the server; pushes them to Langfuse no

The configuration reference is rendered from comments, so the comment above a setting is its documentation: edit the comment, not the page. The docs/reference/ pages are hand-written prose around <!-- BEGIN GENERATED: <name> --> … <!-- END GENERATED: <name> --> regions; only the text between a pair of markers is rewritten, so a page can explain the table it carries.

sdk_py_gen and sdk_ts_gen

The Python SDK stays dict-in, dict-out; the generated file adds the names, so an editor completes agent.call("memory.search", …) and a type checker sees the field names without a hand-kept model layer. The dicts are total=False with Required[...] on the fields the server requires. The TypeScript generator turns each model's JSON Schema into interfaces (enums and consts to literal unions, anyOf to unions, shared $defs hoisted once), so client.call("memory.search", input) is typed end to end. Run them after any change to an operation's registration or its models:

python scripts/codegen/sdk_py_gen.py
python scripts/codegen/sdk_py_client_gen.py
python scripts/codegen/sdk_ts_gen.py

Both SDKs expose every op as a method at the same place: the group is the op name's first segment, the method the rest with . joined, snake_case in Python (agent.client.tracker.source_add) and camelCase in TypeScript (loom.tracker.sourceAdd). tests/unit/test_sdk_parity.py fails when the two op tables, or the two method names of any op, differ.

agent_plugin_sync

The two coding-agent plugins differ only in their manifests and hook wiring (hooks/hooks.json). The Codex plugin carries copies rather than ../ references so each plugin is a self-contained directory a marketplace can check out on its own. After adding a hook script or a skill to the Claude Code plugin, run the sync and commit the copies; wire a new hook into the Codex hooks.json by hand if Codex should run it.

op_reach_census

Every operation declares a maturity and must have a reach path other than a raw HTTP POST, or be declared experimental. The reach kinds (server/operations/reach.py, REACH_KINDS) are the MCP listings (mcp_core, mcp_atomic, mcp_all, derived live from server/operations/toolsets.py), an SDK method (sdk_method), a shipped example or operator script (sdk_example), a plugin hook or integration (plugin_hook) and a dashboard call site (dashboard). The last four come from scanning the tree, which a deployed server does not carry, so they are committed as reach_manifest.STATIC_REACH. --check enforces the rule, that every op without a dashboard call site is in server/operations/dashboard.py (with the reason it is not), and that the committed manifest matches the tree; --write-manifest refreshes it. --call-counts --days 30 adds each op's call count from loom.agent_events (the only mode that connects to ClickHouse), and --owners names the commit that introduced each experimental op's declaration.

Wire changelog

python scripts/codegen/wire_changelog.py <rev_a> <rev_b>           # markdown
python scripts/codegen/wire_changelog.py --since-hours 24 [rev_b]   # rev_a = rev_b's ancestor N hours back
python scripts/codegen/wire_changelog.py <rev_a> <rev_b> --json
python scripts/codegen/wire_changelog.py --snapshot <rev> > snap.json
python scripts/codegen/wire_changelog.py --diff a.json b.json

It materialises each revision with git archive, imports its registry in a child interpreter, and prints the operations and the request and response fields added or removed, plus role, budget and MCP-tier changes. Its output goes into the release notes (release process); the "what changed for clients" section is generated, not written.

gen_py_library_methods

The set of standard-library method names the Python extractor uses is frozen in the tree so an interpreter upgrade cannot move call edges. Regenerating it changes an extractor module, which moves the code index's version stamp and re-chunks every indexed namespace; review the diff, and regenerate only when you mean to.

push_langfuse_prompts

The server links an LLM generation to a Langfuse prompt version by looking up the prompt's content hash in the committed lockfile, with no runtime call. Without --write the script plans against the lockfile offline; --write pushes changed prompts (needs LOOM_LANGFUSE_URL and LOOM_LANGFUSE_ADMIN_TOKEN) and rewrites the lockfile. A unit test fails while the lockfile misses a registered prompt.

The schema dump

schemas/schema.sql is generated by scripts/ops/dump_schema.py from a ClickHouse with every migration applied:

make schema-dump    # python -m scripts.ops.dump_schema
make schema-check   # --check: exit 1 with the head of the diff; writes nothing

It is covered in migrations, since it follows every migration change and must be taken against the ClickHouse version CI pins.

Snapshots that are tests, not generators

The CLI surface (tests/unit/snapshots/cli_tree.json, cli_help.json) and the worker registry (tests/unit/snapshots/worker_registry.json) are pinned by unit tests, and are regenerated by an environment variable on the test run (testing). The CLI reference renders from cli_tree.json, so after regenerating the CLI snapshot, run docs_reference.py too.

Before you push

make lint            # every --check above, plus ruff, the gates and Biome's dashboard accessibility rules

A failure names the script to run; run it, review the diff, and commit the regenerated file in the same change as its source.