Skip to content

majordomus capture

Record the person's raw prompts from a provider hook as a record with its renderings, draw the episode boundary from the provider's own lifecycle hooks, install those hooks, and report what each provider actually does here.

Record the person's raw prompts from a provider hook as a record with its renderings, draw the episode boundary from the provider's own lifecycle hooks, install those hooks, and report what each provider actually does here.

  • generated-output-mutating
  • memory
  • --json

Every prompt is kept three times under one stem: the .json record is the provider's own spans, pretty printed and unchanged, the .md beside it is that record rendered for a person and the .yaml the same for a machine. 'capture render' rebuilds a missing rendering from its record and never writes a record. The schema identifier resolves to share/schemas/<vendor>/<name>/<name>.v<n>, as .schema.json for the record and .proto for the document. 'capture prompt' and 'capture session' never exit 2: they run inside a provider hook, where that exit code can reject the person's prompt. Neither writes to stdout on a start event, because the provider adds it to the model's context. install refuses to rewrite a configuration it did not write, and prints the entries to add.

Syntax

majordomus capture status [--json]

Lifecycle

  1. Set up
  2. Begin
  3. Work
  4. Verify
  5. Conclude
  6. Inspect

Other commands at this stage:context, checkpoint, decision, question, prompt, evidence, skills.

Reads

  • .claude/settings.json
  • .ai/repo/policy.yaml

Writes

  • .ai/local/prompts/
  • .ai/local/session-contexts/
  • .claude/hooks/majordomus-capture
  • .claude/settings.json

Exit codes

Only the codes this command can produce. The full contract is on the commands index.

  • 0 ok
  • 2 usage
  • 12 missing artifact
  • 13 internal error
  • 15 refused

Raw prompt history, captured below the model

A worker cannot record its own prompts: it never sees what the person typed, and a record that depends on a model choosing to write it is missing exactly the prompts that mattered. Capture runs in the provider's hook instead, and the state a provider is in is decided by running that hook, not by finding a file.

Before anything is wired

hook installed
no

Given this repository

# A repository with Majordomus installed and projections generated, and one commit of work.
"$MJ" init >/dev/null
"$MJ" update >/dev/null
mkdir -p lib docs
echo a > lib/a
echo d > docs/d
git add . && git commit -qm base

Run

$ majordomus capture status

Output contains

  • claude-code
  • unconfigured

exit 0

An adapter exists for Claude Code, but this repository does not wire it, and the command says so rather than reporting an empty archive as success.

After the hook is installed

hook installed
yes

Given this repository

# Installed, with the prompt-capture hook in place and majordomus on PATH, which is how a
# repository that is not the tool's own checkout reaches it.
. "$FIXTURE_SETUP/installed.sh"
PATH="$(dirname "$MJ"):$PATH"; export PATH
"$MJ" capture install >/dev/null

Run

$ majordomus capture status

Output contains

  • verified
  • synthetic payload

exit 0

verified is not the presence of a file. The command drove a payload through the shim the provider would run, into an archive of its own, and read the record back.

A provider whose prompts cannot be observed

adapter exists
no

Given this repository

# A repository with Majordomus installed and projections generated, and one commit of work.
"$MJ" init >/dev/null
"$MJ" update >/dev/null
mkdir -p lib docs
echo a > lib/a
echo d > docs/d
git add . && git commit -qm base

Run

$ majordomus capture prompt --provider codex

Output contains

  • no adapter
  • claude-code

exit 12

Exit 12 is MISSING_ARTIFACT, and the message names the providers that do have one. Note what it is not: capture prompt runs inside a provider hook, where exit 2 rejects the person's prompt, so it never returns that code whatever is wrong.

Reference

Record the person's raw prompts from a provider hook — as a JSON record with a Markdown and a YAML rendering of it — draw the episode boundary from the provider's own lifecycle hooks, install those hooks, and report what each provider actually does in this repository.

Why a hook and not an instruction. A worker cannot be asked to record its own prompts. It never sees the bytes the person typed, only what the provider assembled from them, and a record written by a model is missing exactly the prompts that mattered: the first one of a session, which arrives before any instruction has been read, and every one where the model was busy doing what it was asked. A line in AGENTS.md is a request, not a mechanism, and no behavioural test can prove a request was honoured. So capture happens below the model, in the provider's own hook, where running it is the proof that it works.

capture install writes a shim per event and the matching entries in .claude/settings.json, and refuses to overwrite any of them: majordomus-capture for UserPromptSubmit, majordomus-session-start for SessionStart and majordomus-session-end for SessionEnd. When the configuration exists and does not name one of them — including a configuration this tool wrote before it knew about the lifecycle events — the command prints the entries to add, one per missing event, and exits 15 rather than rewriting a file it did not write. A shim finds the repository from its own location: the provider substitutes its project directory into the command string textually, so nothing in the environment names the repository, and the working directory a hook runs in is not contracted.

capture prompt reads one JSON payload on stdin and writes one record. It never exits 2, because in UserPromptSubmit that exit code rejects the person's prompt, and a broken archive must never cost someone their input; a payload it cannot read is written to the archive's own log instead — and doctor fails while that log is non-empty, because a capture that failed means prompts were lost. That log is the only thing that can catch the failure that matters: if the provider renames the field the prompt arrives in, the hook still runs and the self test still passes, since it sends a payload of the tool's own making. Read the log and delete it; it is a diagnostic, and unlike a record, nothing is lost by removing it. The prompt text itself is the raw span from the payload, copied through still escaped, so no decode and re-encode step can lose a character.

One prompt is two files under one stem, .ai/local/prompts/YYYYMMDDHHMMSS-<slug>.json and .md, where the slug is the opening of the prompt itself:

.ai/local/prompts/
  20260905113342-why-arent-prompts-saved-automatically.json
  20260905113342-why-arent-prompts-saved-automatically.md
  20260905114501-make-it-a-file-per-prompt.json
  20260905114501-make-it-a-file-per-prompt.md

Both formats, always, and they are not alternatives. The .json is the record: the provider's own spans, still escaped, pretty printed one member per line — what everything else is derived from and what capture must not lose. A record is read by people too, and a single forty-kilobyte line is not something any editor or diff shows usefully; the indentation lies outside the string spans, so the prompt's bytes are untouched by it. The .md is that record rendered for a person, in one shape every rendering has: the fields as YAML front matter, the same fields again as a table, and the prompt last under ## PROMPT, decoded and fenced.

---
schema: 'majordomus.capture/v1'
ts: '2026-09-05T12:48:55Z'
provider: 'claude-code'
event: 'UserPromptSubmit'
id: 'bb6f8c96'
session: '5b13785c-728f'
source: 'user'
cwd: '~/src/your-repo'
repository: '~/src/your-repo'
branch: 'feature/prompt-capture-markdown'
head: 'a0ccbc9e25d6ec7f6bb754f3515affb9b1cc4014'
record: '20260905124855-make-it-a-file-per-prompt.json'
---

# Prompt — 2026-09-05 12:48:55 UTC

| | |
|---|---|
| **Started** | `2026-09-05T12:48:55Z` |
| **Provider** | `claude-code` · `UserPromptSubmit` |
| **Session** | `5b13785c-728f` |
| **Prompt** | `bb6f8c96` |
| **Source** | `user` |
| **Repository** | `~/src/your-repo` |
| **Branch** | `feature/prompt-capture-markdown` · `a0ccbc9` |
| **Directory** | `~/src/your-repo` |
| **Schema** | `majordomus.capture/v1` · `share/schemas/majordomus/capture/capture.v1.proto` |
| **Record** | `20260905124855-make-it-a-file-per-prompt.json` |

Evidence

Every case below runs in CI on Linux and macOS. The site refuses to build if a public command has no behavioural case, no negative case, or no demonstration.

Guarantees this command carries