Out-of-Band Audit Channel

The Problem¶
The agent that just shipped a feature is the worst possible reviewer of its own discipline. It will mark its own work complete, label deferred docs as "Phase 2," and skip past its own CONVENTIONS.md rule with conviction. Mid-task tunnel vision suppresses the rules it read at session start.
You cannot fix that with more advisory prose: the same
convention that didn't stop the agent the first time won't
stop the next agent either. What works is mechanical
verbatim relay — the same channel ctx already uses for
ctx remind, journal-import nudges, and knowledge-growth
warnings. Agents echo those without filtering, every turn,
because the relay bypasses judgment.
This runbook shows how to run discipline audits out of band (from a separate Claude Code session, on your plan-billed subscription, not the working session's API) and drop their findings onto the verbatim-relay channel so the next interactive session sees them at the top of its next turn.
Maintainer tooling: lives in ctxctl, not the shipped ctx binary
ctxctl audit and the ctxctl audit-relay hook are the
generic relay half: a place for any out-of-band tool
to drop a report and have it relayed. They live in
ctxctl — ctx's separate maintainer/contributor binary —
not in the user-facing ctx binary, so end users
never carry an audit hook they have no producer for. The
auditor that produces the report is project-specific —
it must know your conventions and directory layout. ctx
dogfoods its own internal auditor (_ctx-surface-audit,
a repo-only skill that scans ctx's internal/ tree); the
examples below use it as a concrete reference. To adopt
the pattern in your own project, build the same
out-of-band relay plus your own audit skill. ctx
maintainers build and install ctxctl once with make
reinstall-ctxctl (→ /usr/local/bin/ctxctl); every
worktree then shares the one binary.
TL;DR¶
# 1. From a separate Claude Code session, run your project's
# audit skill (ctx's own internal example shown here):
/_ctx-surface-audit # default: main..HEAD
# 2. It writes a structured report:
.context/audit/surface.md
# 3. Back in the working session, the next prompt fires the
# repo-local UserPromptSubmit hook (wired in
# .claude/settings.local.json, not by `ctx setup`):
ctxctl audit-relay
# 4. The agent / human sees a verbatim-relay box on the next
# response, listing the specific findings.
# 5. After addressing the findings:
ctxctl audit dismiss surface # stops the relay
Commands and Skills Used¶
| Tool | Type | Purpose |
|---|---|---|
ctxctl audit list |
CLI command | Show all reports with status and age |
ctxctl audit show ID |
CLI command | Print one report's body, pipe-friendly |
ctxctl audit dismiss ID |
CLI command | Mark a report dismissed against its current digest |
ctxctl audit dismiss --all |
CLI command | Bulk dismissal |
ctxctl audit-relay |
CLI command | UserPromptSubmit hook; verbatim-relays reports |
_ctx-surface-audit |
Skill | ctx's own internal auditor — reference example, not bundled |
Why a Separate Session¶
Two reasons, both load-bearing:
- Fresh-context judgment. The auditor must not inherit the implementer's working memory of "what we tried, what we decided to defer, why this is fine." The audit only works if the reviewer reads the diff cold.
- Cost shape. A per-commit AI gate burns API tokens on every commit, regardless of branch maturity. Running the auditor manually from a separate Claude Code session bills against your interactive plan, not the API, and lets you decide when to spend the cycles (typically right before a PR, not on every micro-commit).
The /_ctx-surface-audit skill enforces this with a hard
dirty-tree refusal: invoking it in a working session with
uncommitted changes returns
Run this audit from a separate Claude Code session.
There is no override flag, by design.
The Workflow¶
Step 1: Land Your Work, Then Open a Second Session¶
Finish the feature on your working branch (commit, lint,
test). Open a second Claude Code window in the same project
worktree. The audit runs against main..HEAD by default.
Step 2: Invoke the Auditor¶
You (in session 2): "/_ctx-surface-audit"
Skill: "Scanned 4 commits, 3 surfaces detected.
Wrote .context/audit/surface.md (status: findings).
Open a working session — the audit-relay hook will
relay the findings on the next prompt."
The auditor compares the branch against main, finds new
subcommands / flags / behavior changes, checks each one
against SKILL.md / recipe / docs/cli coverage, and writes
a structured report.
Step 3: Return to the Working Session¶
The next time you submit a prompt in your working session,
the ctxctl audit-relay hook (a UserPromptSubmit hook wired
in the repo-local .claude/settings.local.json — maintainer-
only; ctx setup does not install it) reads
.context/audit/ and emits a verbatim-relay box at the top
of the agent's response:
┌─ Audit Reports ──────────────────────────────────────
│ [surface] main..HEAD
│ Commit 6bcaf889 added user-facing surface without docs:
│
│ • New subcommand `ctx pad undo`
│ - SKILL.md: internal/assets/claude/skills/ctx-pad/SKILL.md
│ command-mapping table is missing the row
│ - Recipe: docs/recipes/scratchpad-with-claude.md unchanged
│
│ Fix:
│ - edit internal/assets/claude/skills/ctx-pad/SKILL.md
│ - edit docs/recipes/scratchpad-with-claude.md
│
│ Dismiss: ctxctl audit dismiss <id>
│ Dismiss all: ctxctl audit dismiss --all
└──────────────────────────────────────────────────
The agent echoes this verbatim — that is the discipline mechanism. You (or the agent) then address each cited file.
Step 4: Dismiss¶
Once you've addressed the findings (or accepted them as out-of-scope), dismiss the report:
Dismissal is bound to the report digest at dismiss time. A subsequent audit that produces the same findings stays dismissed. A subsequent audit that finds new surface drift produces a fresh digest and re-surfaces the report at the next prompt.
Retention¶
The audit channel keeps one report per kind. Re-running
/_ctx-surface-audit overwrites the prior surface.md.
Reports older than 30 days are still relayed but prefixed
with a STALE — main..HEAD (audited 32d ago) marker so the
recipient knows the assessment may not match current code.
History (which audits ran when) is preserved by the
dismissal ledger at .context/audit/.dismissed.json. The
ledger lives next to the reports — not under
.context/state/ — so nuking session state never silently
re-surfaces a dismissed audit.
When to Run the Auditor¶
- Before opening a PR. The natural cadence. The audit exists to catch the gaps you can't see in your own branch.
- After landing a multi-commit feature. Especially when the feature added new subcommands or flags.
- Periodically on
main, with a longer range likeHEAD~50..HEAD, to catch surface drift that crept in before this channel existed.
There is no automated trigger in Phase 1. The cost shape is intentional: cron and post-commit-hook drivers stay on the deferred list until the user-driven workflow proves out.
Other Audit Skills¶
_ctx-surface-audit is the first of a family of ctx's own
internal auditors (all _-prefixed, repo-only). The
scaffolding they share — channel, ledger, hook, CLI — lives
in the maintainer-only ctxctl binary; the auditors
themselves are repo-only skills. Planned
siblings under the same shape:
_ctx-spec-trailer-audit— does each commit'sSpec:trailer point at a spec that genuinely covers that commit's scope?_ctx-capture-audit— was a Decision or Learning persisted for non-trivial work that ended without one?
Each lives in its own SKILL.md and writes its own report
file (e.g. .context/audit/spec-trailer.md). The hook
relays whatever it finds, with no per-kind plumbing — which
is exactly what lets your project's auditors plug in
without touching ctx.
See Also¶
- Spec: out-of-band audit channel: full design rationale + Open Questions
- CONVENTIONS → User-Facing Surface Completeness: the canonical rule the surface audit enforces
- Detecting and Fixing Drift: programmatic drift detection that complements judgment-based audits