Codebase documentation
Point an agent at your source tree and have it write the documentation nobody gets around to: an architecture guide, an onboarding page, a module reference — grounded in the code it actually read, and regenerated by a command instead of a person.
Prerequisites
-
contenox initonce in the repository, and a model configured — see Quickstart. -
An envelope.
contenox runfires a mission, and a mission that names no envelope is refused:contenox config set default-mission-policy hitl-policy-default.json
Nothing else. local_fs and local_shell are hosted by contenox and carried by
the client that runs the agent, so reading your own tree needs no filesystem
server registered and no flag enabling it. The workspace is the directory you
started the command in.
The agent
.contenox/agents/docs-writer.md:
---
name: docs-writer
description: Writes and updates documentation from the source it read in this repository
tools: Read, Write, Edit, Bash, git.git_diff, git.git_log, git.git_show
---
You document code you have read. A claim you cannot point at a file for does
not go in the document.
PROCEDURE: find the shape of the tree first — `ls` at the top level, then
descend deliberately. Never enumerate blind: exclude `node_modules`, `dist`,
`vendor`, `target` and `.git` by name from every `find`, and pass
`--exclude-dir` to `grep`. Read entry points before helpers, and follow imports
rather than guessing at what a package is for.
Prefer local_fs.read_file over cat and head: it is bounded and it respects the
policy that cat does not. Use the shell for what has no dedicated tool — find,
grep, and running a command whose output you intend to quote.
WRITE the document to the path you were given, creating the parent directory if
it does not exist. Structure it so a reader who has never opened this repository
can find their way: what the thing is, the packages and what each owns, the
paths a request takes through them, and where the state lives.
DO NOT invent. When two readings of the code are both plausible, write the one
you can cite and say plainly that the other is unverified. A confident sentence
about code you did not open is the worst thing you can leave behind.
FINISH with what you wrote, which files you read to write it, and anything you
could not establish from the source.
The tool list is the interesting line. Read, Write and Edit are the
local_fs tools; Bash is local_shell; the three git.git_* entries are the
read-only half of the git toolset — enough to see what changed, and nothing that
can commit it. Omitting tools: entirely would inherit every tool on the
machine instead.
contenox agent list # the next run picks the file up; there is no build step
Recipe 1: an architecture guide
contenox run docs-writer \
"Read the Go sources under ./internal. Write docs/architecture.md: the top-level packages and what each owns, the data flow between them, and where state is persisted."
The agent lists the tree, reads what it needs, writes the file, and reports what it read. Stdout carries that report — the file is the artifact, and the report is how you know it landed.
Recipe 2: an onboarding page for a new hire
contenox run docs-writer \
"Read ./README.md, ./docs/ and ./go.mod. Write docs/onboarding.md: what this project does, how to run it locally, the concepts a newcomer needs, and three first tasks that touch real code."
Recipe 3: keep the docs honest after a change
This is the one that pays for the declaration. The agent has git_diff and
git_log, so it can establish what moved before deciding what to rewrite:
contenox run docs-writer \
"The last commit refactored the retry path. Read its diff, then read the current text of every file it touched, then update the affected sections of docs/architecture.md. Change nothing the diff did not."
Or let a merge trigger it. .git/hooks/post-merge:
#!/bin/sh
contenox run docs-writer \
"Read the diff of the merge that just landed, then update any section of docs/ it made wrong. Change nothing else." \
--timeout 15m
An agent asked to update rather than regenerate does less damage to a hand-written page, but it will still touch what it believes is stale. Run it on a branch and read the diff it produced — the same way you would review any other automated edit.
Recipe 4: a module reference
contenox run docs-writer \
"List the exported functions in ./internal/payments. For each, read its signature and doc comment. Write docs/reference/payments.md as a table: Function | Signature | What it does."
The piped form
For a one-off question about a diff, no declaration is involved:
git diff | contenox "which of these changes make docs/architecture.md wrong?"
The diff is the material the question is about, and the answer comes back on
stdout. Reach for the pipe when you want to know something; reach for
contenox run docs-writer when you want a file written.
Publishing straight to Notion
Register the server once, then grant it to the agent:
contenox mcp add notion https://mcp.notion.com/mcp --auth-type oauth
contenox mcp auth notion
contenox mcp auth is a separate, required step — add registers the server,
it does not authenticate it. See Notion as a tool
for the full server surface.
Give a second declaration the grant, .contenox/agents/docs-publisher.md:
---
name: docs-publisher
description: Writes documentation from the source and publishes it to Notion
tools: Read, Bash, notion.create_page, notion.update_page, notion.search
mcpServers: [notion]
---
You document code you have read, then publish it.
Read the source you were pointed at before writing a line about it, the same
way you would for a file on disk. Then search Notion for a page with the title
you were given: update it when it exists, create it when it does not. Never
create a second page with a title that is already taken.
Report the page URL and the files you read. Nothing else.
contenox run docs-publisher \
"Read ./internal. Write an architecture guide and publish it to Notion as 'Architecture: payments-api'."
Note what this declaration does not carry: Write and Edit. It publishes
to Notion, so it has no business writing to your disk, and the tool list is
where that is settled rather than the prompt.
Two things to know before you script it
A gated call has nobody to ask. There is no terminal in front of
contenox run, so a call the envelope gates becomes a
durable ask and the run
waits on that row until someone answers it with contenox approvals respond —
the answer releases the waiting call and the run carries on — or until
--timeout (default 30m) tears it down with a non-zero exit. If a documentation
run keeps timing out, the envelope gated the write; widen the envelope, not the
prompt.
Scope the read. A large repository will not fit in a context window, and an
agent told to “read the codebase” spends its budget on node_modules. Name the
packages you mean. The bounds it works inside — the context budget, the tool-call
ceiling, the loop rounds — live in agents.toml
([chain] token_limit, [policy.compute] max_tool_calls), not in the
declaration.
The run leaves a durable record either way:
contenox mission list
contenox mission reports <mission-id>