Agents
There are two different things an agent can do with your codebase.
It can write code in it — read some files, follow the conventions it finds, open a diff. Most tooling supports this, because it only requires the repository to be readable.
It can operate it — know what a change affects before making it, run the right jobs, read the result as data, tell you what is not production-ready and why. That requires the system to state what it is, in a form something other than its author can parse.
Putnami is built for the second one. This page is the contract that makes it possible. For the practical setup — assistant context files, prompt patterns — see Develop with AI assistants.
Orientation: read the system, not the files
When you run putnami init, the workspace gets one short Putnami block in
AGENTS.md (which a new CLAUDE.md imports), the declarations of the
@putnami/contributor extension's agent content (its skills and agents land
once the extension is published and installed; see
Develop with AI assistants),
and a .mcp.json entry that registers the Putnami MCP server. That is the whole
of what Putnami writes. Your own durable rules go in .agents/constraints.md, a
file you create yourself — the block tells an
assistant to read it when it exists, and Putnami never writes it. Adding an
extension later? putnami context generate refreshes the block and the skills.
That teaches an assistant your conventions. Two commands teach it your system.
putnami context map — where things are
putnami context map # regenerate
putnami context map --print # render to stdout, write nothingThe workspace orientation map, rebuilt from committed artifacts — project
manifests, schema/openapi.json, schema/config.jsonschema.json, READMEs, and
the docs tree. It answers "which project owns X", "what depends on Y", "where
are the APIs, the config keys, the docs" without shell exploration.
It is byte-deterministic on an unchanged tree, lives gitignored under
.putnami/context-map/, and is never committed. putnami build refreshes it
automatically outside CI, so it does not go stale behind the code.
putnami context pack — how one project is composed
putnami context pack --project <project-id>It writes one deterministic agent-context document per project to
<project>/.gen/agent-context.json:
- identity and graph — id, name, path, type, tags, languages, and the ids of dependencies and dependents;
- composition roots — the application main, the describe entrypoint, and other roots, each with a path and a provenance tag;
- capabilities, contracts, infra, migrations — references carrying a path
and a
sha256:digest, pointing at the committed artifacts; - representative sources — ordered ranges (
path,startLine,endLine, a reason, a token estimate). Ranges only; - tests — the policy, referenced conformance packs, and fixture digests — or, when there are none, a machine-readable reason for their absence;
- docs — adjacent documentation, each marked checked or unchecked;
- config — a config-schema reference and refs-only hints about the operational surface;
- provenance — the workspace revision, the generator, and the aggregation method.
Three properties are worth stating plainly, because they are what make the document safe to hand to a tool you don't control.
It aggregates by reference. Paths, digests, and line ranges. Never file content. The types have no slot for content, and the safety gate rejects any string long enough to look like it anyway.
It is ephemeral. It lives under the gitignored .gen/ root and is never
committed — it embeds the workspace revision and content digests, so it would
churn on every commit. Determinism is pinned byte-for-byte by tests at a fixed
tree, not by committing the artifact.
Publication fails closed. Before a document can leave the workspace for an authorized index, a redaction gate runs and reports as hard errors: a reference to a sensitive path that is not flagged sensitive, anything resembling embedded content, any path that is absolute or escapes the workspace, and any duplicated reference. An empty document fails the gate, so it can never pass vacuously.
You can adjust the document, narrowly. <project>/schema/agent-context.overrides.json
adds or removes representative-source and doc entries and force-flags paths as
sensitive. It cannot override identity, capabilities, or provenance — those are
framework-owned facts, and an author who could rewrite them could make the
document lie.
In CI or pre-flight:
putnami context pack --check # re-derives, exits non-zero on drift, writes nothingThe MCP server
putnami mcp install # writes or repairs the putnami entry in .mcp.json, merge-aware
putnami mcp # run the server over stdio (your client spawns this)putnami init, putnami install and putnami upgrade add the putnami entry
to .mcp.json when it is missing. Other servers and unknown keys in an existing
file are preserved, a putnami entry you changed is kept, and a file that does
not parse is left untouched with a warning. mcp install writes the same entry
on request, and is the one command that rewrites a diverged one.
The server speaks JSON-RPC 2.0 on stdin/stdout, exits with the session, and exposes the workspace as typed tools instead of shell exploration:
| Tools | What they answer | |
|---|---|---|
| Orient | list_projects · describe_project · agent_context · workspace_map |
What is in this workspace, how is this project composed, where should I read first, which project owns X |
| Blast radius | impacted · why_impacted · deps · find_owner · topo_sort |
What does my change affect, and by what path |
| Intent | sdd.list_features · sdd.feature_context · sdd.list_specs · sdd.spec_context |
What was this built for, what were the non-goals, what did the team agree to |
| Execute | run_jobs · get_diagnostics |
Run lint / test / build, then read every failure as file:line:col with severity and code |
Plus the workspace://context resource, so a client that prefers resources over
tool calls gets the same facts.
Every graph answer says what it was derived from
Project identity and dependency edges belong to the language extensions. The CLI
keeps a local copy in .putnami/workspace-index.json so a read-only tool answers
without paying for a probe — and since that copy can be missing or old, every
graph answer carries a workspaceView block saying which:
"workspaceView": {
"freshnessState": "fresh",
"observedAt": "2026-09-01T09:12:44Z",
"probeDigest": "sha256:…",
"ageSeconds": 3420,
"message": "the recorded workspace view (workspace-index.json) was observed 57m ago"
}Three states, and the tool behaves differently in each:
freshnessState |
What the tool does |
|---|---|
fresh |
Answers normally. |
stale |
Answers anyway, and the block says the copy is past its bound. |
absent |
Returns an error result carrying the partial answer and the block, whose message names putnami projects sync. |
The last row is the important one. A workspace nobody has probed used to answer an empty-but-well-formed graph, which reads as "there is nothing here" rather than "nobody has told me yet". It is now a refusal an agent can act on.
This is the consumer half of a declared architecture contract
(cli.workspace-probe-view.v1 in tooling/cli/putnami.architecture.json): the
freshness bound, the fail-closed-on-missing behavior and the field names above
are declared there and joined to the code by a conformance test.
Every tool declares its own contract
Each tool carries a putnami.dev/contract block in its metadata: an access
value (read or mutating), a readOnly flag, whether it supportsDryRun,
and — when it mutates — a mutates list naming exactly what it touches.
run_jobs is the only mutating tool. Everything else is read-only. It runs
jobs through the same planner and engine as the CLI, declares that it mutates
the workspace and the cache, supports dryRun to return the selected dependency
plan without starting a subprocess, and refuses long-lived serve mode and any
job declared to mutate external systems.
An agent doesn't have to guess which call is safe. The tool says so.
Optional request provenance
Off by default. Set PUTNAMI_AGENT_IDENTITY=1 and explicitly set
PUTNAMI_AGENT_MODEL in the MCP server environment, and requests carry bounded
harness and model headers — so what an agent did is attributable after the fact,
by the same principle that makes every other action in Putnami reviewable.
Blast radius before action
putnami lint,test,build --impacted--impacted resolves the projects affected by the current changes against a
baseline, transitively through the dependency graph. The baseline is resolved in
a fixed order — workspace config, nearest configured epic branch, trunk, local
main or master, then the upstream tracking ref — and never the current branch
itself, so it cannot quietly resolve to "nothing changed".
Run it once, before declaring the change complete. It also runs every project
that depends on the change, so while iterating, select only the projects you
changed with --projects <a>,<b>. The generated AGENTS.md gives agents the
same rule.
When an agent needs to justify the result rather than trust it, impacted
returns a reason per project (the changed file that claimed it, or the edge
that reached it), why_impacted returns the shortest path from the changed
project to the impacted one over the same edges with each edge's kind, and
find_owner maps a file path back to the project that owns it using the same
logic.
Deliver in gated phases, not in small pull requests
An --impacted run ends with the size of the change, split into authored code,
tests, docs and generated files. The size is information: no line count fails
the run. A change that updates its tests and docs is larger by design, and
agent work, such as an epic, is often large and legitimate.
What keeps a large change safe is how it lands:
- One pull request carries one intent.
- Work that spans several projects goes on an integration branch listed in the
workspace
epicBranches, one phase per commit. - Every phase passes the gate before the next one starts.
- The branch merges as one unit that one revert undoes.
When the authored code of a change spans projects that no dependency relates,
and the branch is not on an epic branch, the run prints a mixed-intent warning.
The warning never changes the exit code. Both are also recorded as a
selection:change-shape session event. The managed plan, fix, execute
and epic skills follow the same rule.
A surface that parses the same way every time
putnami build --output=json
putnami test --output=jsonlExit codes, output modes, the reserved global-flag registry, and the machine
documents are the cli protocol — versioned, and validated
by one conformance corpus executed from both the Go and the TypeScript
implementation. An agent that learned the shape does not have to relearn it
because a release changed a log line.
Every job also emits one JSONL event stream — logs, progress, phases, diagnostics, metrics, artifacts, results — so the agent watching a build and the human reading the log are parsing the same lines.
Verifying the work
Generating a change is the easy half. The useful half is telling whether it is safe.
putnami doctor --profile productiondoctor is a read-only production-readiness preflight. It derives findings from
committed manifests — incomplete capabilities, missing required config, invalid
committed schemas, config shadowing — and grades them by deployment profile.
Under --profile production a high or critical finding exits 2; dev and
test stay advisory. Config values are never read or emitted, so running it
in a pipeline leaks nothing.
Each check carries a remediation baked into the check itself, and the code
taxonomy is frozen. Findings you have consciously accepted go into a committed
doctor.waivers.json — a waiver is a reviewable decision in git, not a flag
someone remembered to pass.
Alongside it: get_diagnostics re-reads the complete failure set from the last
executed run without rerunning the work, conformance packs test declared
capabilities against shared fixtures, and putnami context pack --check keeps
the orientation document from going stale behind the code.
Agent readiness, marker by marker
putnami agent-readiness measures how far a repository lets an agent work
safely. It reads twelve markers, grouped in four steps, and grades each one.
The
agent-readiness method
says how. The method is neutral: it recognizes a tool by what the tool does,
and a Putnami command counts the same as any other tool.
This table says what each Putnami default gives each marker:
| Marker | What Putnami does by default |
|---|---|
understand.instructions |
putnami init writes a Putnami block in AGENTS.md that names the command to run and the gate to pass. |
understand.commands |
putnami ci init writes putnami.ci.json, which runs lint, test and build on every change, and validate when the workspace declares @putnami/sdd. |
understand.area-docs |
Every project template writes a README.md, and the lint-docs step of putnami lint fails on a broken relative link or anchor in a project's README.md files and doc/ tree. |
bound.declared-areas |
Each project declares itself in its putnami.json, and putnami.workspace.json marks the workspace root. |
bound.boundary-rules |
A project is importable only from its own scope unless its visibility widens it, and an import that crosses a boundary fails every command that plans over the graph. |
bound.cross-area-changes |
--impacted runs what a change reaches; how often a change crosses areas depends on how you work, not on a default. |
verify.tests |
putnami test runs the tests of every selected project, and --enforce-coverage fails the gate below the coverage threshold. |
verify.static-checks |
putnami lint runs the linters of each language extension in the gate. |
verify.reliable-signal |
The Go and TypeScript lint refuses a skipped or focused test that no platform or dependency guard explains; a reviewed exception carries putnami:allow-skip <reason>. |
verify.pinned-toolchain |
putnami.lock.json pins each extension and template, and putnami pin adds the CLI to that lock. |
recover.ownership |
With @putnami/sdd, validate-workspace writes .github/CODEOWNERS from the options.sdd.owners each putnami.json declares, and CI fails when the committed file drifts. |
recover.small-changes |
An --impacted run reports the size of the change by category and warns on mixed intent; no line count fails it. See Deliver in gated phases. |
Two markers need a declaration from you: recover.ownership needs the owners,
and verify.pinned-toolchain needs a CLI pin. Some markers grade how long a
check has been enforced, so a new workspace reaches their top level only after
90 days of use.
The documentation is part of the surface
Stable URL prefixes per section, Copy as Markdown on every page, and
/llms.txt as a machine-readable index of the whole tree. The
structure an agent reads is the structure you read.
What this deliberately does not do
- The agent-context document is per project, and it references rather than embeds. It is an orientation artifact, not an index of your repository — an agent still reads the files it decides to read.
- The MCP server runs workspace jobs. It does not deploy, does not reach external systems, and refuses jobs declared to do so.
- Author overrides are narrow on purpose. If you could rewrite identity or capabilities, the document could lie, and every guarantee above would be worth nothing.
- None of this makes an agent correct.
That last one matters. What the contract buys you is that an agent's actions are bounded, inspectable, and reversible — which is the precondition for letting one run at all.
Read next
- Protocols — the contracts underneath everything on this page.
- Develop with AI assistants — the practical setup and prompt patterns.
- Write a feature spec — how intent gets recorded so
sdd.spec_contexthas something to return. - Spec-driven development — the loop that turns declared intent into a CI verdict, and the architecture contracts (ARC and DARC) that bound what agents may couple.
- Principles — in particular, automation is a first-class user.