How docket works
Everything on this page is implemented, tested, and reproducible from the repo. The normative format definition is the Loop File Spec; this page is the guided tour.
What docket is
AI agents act now — they send email, move meetings, file tickets, change records. When an agent misreads you, you don’t get a wrong paragraph back; you get a wrong action with your name on it.
Docket is the permission layer and the paper trail. You describe each recurring task in one Markdown file (a loop). Before the agent acts, it checks the loop’s warrant and gets a deterministic verdict — allow, ask, or deny. After it acts (or stops), it appends what happened to a hash-chained record that exposes any tampering.
Three design commitments shape everything else:
- Unlisted means ask. The default verdict is the safety property. An agent that encounters something you didn’t anticipate has, by definition, encountered something that needs a human.
- Plain files, no platform. Rules and record live in your repo as Markdown
and JSONL.
grepworks,git diffworks, deleting docket loses you nothing but the tooling. Zero runtime dependencies. - Describe, don’t execute. Docket is not an agent framework. It’s the layer underneath whichever agent you already use — Claude, ChatGPT/Codex, Gemini, Cursor, OpenClaw, Hermes, or anything that speaks MCP.
Quickstart
npm install -g docket-agent # or use npx docket-agent <command> cd your-project docket init # creates .docket/ docket new appeal --template insurance-appeal docket compile --target claude --write # or agents / gemini / cursor
Then try the warrant:
$ docket check appeal draft "appeal letter" ALLOW "appeal letter" is within the draft warrant. $ docket check appeal send "appeal email to the insurer" ASK unlisted means ask — silence is never permission. $ docket check appeal change "accepting a settlement" DENY matches a hard stop. Never happens, with or without approval.
Run docket new <name> without a template to get the five-question
interview instead, and docket templates to list all eight starters.
Make it ambient in a repo
One command wires docket into the whole repo so every agent that touches it is under the warrant — context and enforcement, committed with the code:
docket install # compile context for every agent + hook + MCP configIt compiles the loops into CLAUDE.md, AGENTS.md,
GEMINI.md, and Cursor rules (read automatically at session start — zero
setup for anyone who clones), writes a Claude Code PreToolUse hook into
.claude/settings.json (the mechanical gate), and
adds a .mcp.json for native tools. It’s merge-safe (existing hooks and
MCP servers are preserved) and idempotent — add a loop, re-run, the context
refreshes. Flags: --loop <name> to pin one loop, --strict
to ask on uncovered calls, --no-hook / --no-mcp for
context-only, --targets claude,agents to narrow the context files.
Loops & the five layers
Don’t configure an assistant. Bound one recurring task — the insurance appeal, the client follow-up, the weekly plan. Each loop wraps that task in five layers:
| Layer | Question it answers | Lives in |
|---|---|---|
| brief | What must the agent know before it starts? The context that changes the answer: people, history, constraints, standards, past decisions. | Markdown body |
| procedure | How is this job done properly? Which sources count, what finished looks like, the known ways it goes wrong. | Markdown body |
| warrant | What may it do without asking? Read / draft / change / send lists, plus always-ask and never. | YAML frontmatter |
| record | What evidence must it leave behind when it finishes or stops? | YAML frontmatter |
| reserved | What stays with the human, always? Accounts, secrets, spending, final sign-off. | YAML frontmatter |
Prose layers live in the body because humans write and diff prose well; machine layers live in frontmatter because tools enforce structure well.
The loop is an agent contract
Addy Osmani’s Agentic Autonomy Levels argues that “every run of an agent should be preceded by a contract” — goal, scope, permissions, stopping condition, evidence, budget — and that “the autonomy level should follow the verification process, not the task name.” A loop is that contract. Three optional frontmatter fields complete it:
| Field | Answers | Enforced? |
|---|---|---|
| goal | The outcome to reach — not an activity. | descriptive (in context) |
| stop | The conditions that end the run; the agent halts at any of them. | descriptive (in context) |
| budget | The ceiling on the run — tokens, attempts, parallelism, time. | descriptive (in context) |
goal/stop/budget are compiled into the agent’s
context so it knows its own contract, but docket doesn’t execute — it can’t count tokens
or evaluate a stop condition. Enforcement of actions stays the warrant’s job;
these bound the run, and belong to whatever harness runs the agent. Writing the
contract down is what makes an autonomy level defensible.
Writing a loop file
A loop is a single file, <name>.loop.md, in
.docket/loops/:
--- name: insurance-appeal description: Build the appeal, cite the policy — stop before send. triggers: - insurance appeal, appeal a denial - denied claim, denial letter warrant: read: [policy documents, denial letter, claim correspondence] draft: [appeal letter, evidence summary] change: [] send: [] ask: [contacting the insurer, requesting new records] never: [accepting or rejecting a settlement] reserved: - signing and sending record: - every policy clause cited, with section numbers - where the draft stopped and what a human must do next --- # Brief The denial reason code, the claim timeline, the appeal deadline… # Procedure Read the denial letter first. Answer the stated reason, not a general sense of unfairness. Stop before send.
Rules the parser enforces
namemust be lowercase letters, digits, and dashes — and must match the filename, or record entries would point at a loop that can’t be loaded. Mismatches are rejected.- Headings named exactly
BrieforProcedure(any of#/##/###, case-insensitive) open the prose layers. Every other line — subheadings, code fences, comments inside fences — is content and stays with its section. Nothing you write is silently dropped. - Frontmatter is a deliberately small YAML subset: nested maps, lists of scalars, quoted strings, numbers, booleans. No anchors, no multi-line scalars — a grammar small enough to audit is part of the security posture.
- A
versionthe tool doesn’t understand is rejected, never silently misread. triggersis optional routing metadata — phrases that mark a task as this loop’s job. It never grants permission; it only helps routing find the loop.
The warrant
Four verbs, in escalating order of consequence:
| Verb | Meaning |
|---|---|
| read | look at it |
| draft | produce it, but it goes nowhere on its own |
| change | mutate state that stays inside the sandbox |
| send | consequences leave the sandbox: email, publish, file, deploy, pay |
Plus two cross-cutting lists: ask (always needs human approval,
whatever the verb) and never (does not happen, even with approval — you,
under calm conditions, pre-deciding what no in-the-moment persuasion may undo).
scheduled or automated sending and
git hooks, CI workflows, or scheduled jobs.The verdict algorithm
Given a loop, an action verb, and a target (a plain-language description of what the action touches), the verdict is the first rule that matches:
- target matches
never→ deny - target matches
ask→ ask - target matches the action’s own list → allow
- otherwise → ask
Matching semantics
Matching is case-insensitive and asymmetric by design: a phrasing difference may cause an unnecessary ask; it must never cause an accidental allow.
- A pattern containing
*is a glob over the whole target. - Commas,
or, andandsplit a pattern into alternatives (secrets, tokens, or passwordsis three patterns). - Words compare under light stemming (
quotes≈quote,contacting≈contact), and filler words (a,the,anything…) don’t count. - Cautious mode (ask/never): substring or word-subset match in either direction — ambiguity escalates to you.
- Strict mode (allow lists): the target must cover the pattern.
The vague target
"email"never inherits permission from the specific entry"status email to the team". - An all-filler pattern like
never: [anything]matches everything — its plain meaning.
Exit codes are the contract
docket check exits 0 for allow, 2 for ask, 3 for
deny (1 for usage errors) — so shell hooks, wrappers, and CI can gate on the warrant
directly.
Baselines & inheritance
A platform team has rules that hold everywhere: never commit a secret, never run destructive commands in production, always ask before spending money. Copying those into forty loop files gives you forty places to forget them — and no way to answer “is that rule actually in force everywhere?” without a grep and a hope.
--- name: deploy extends: org-baseline # ← the floor, in one file warrant: change: [staging environment, feature branches] ---
A bare name resolves to a sibling in the same loops directory; a ref with a
/ or ending in .loop.md resolves relative to the extending
file, so a vendored or submoduled baseline keeps working in every checkout.
The merge rule is one sentence
Every list is a union, parent entries first. That single rule is what makes
inheritance safe, and the reason is structural rather than careful — the verdict order
is never, then ask, then the action’s allow list. A union can
only add entries, and the two lists consulted first are the restricting ones:
- A child cannot delete a parent’s
never. It is still there, still checked first. - A child that allows something the parent asks about does not win — the
parent’s
askis reached first, and the child’s entry is simply never consulted.
So a child may widen only into space the baseline left open, and a baseline closes
space by writing ask or never — not by keeping its own allow
lists short. There is deliberately no override escape hatch: a baseline a child
can opt out of is a suggestion, and this field exists because a suggestion was not
working.
Verdicts name the policy that produced them
$ docket check deploy change "drop the production users table" DENY change → "drop the production users table" … matches a hard stop. This rule is inherited from the "org-baseline" baseline.
That from lands on the record too. “Which policy stopped this?”
is the first question at an audit and should not take re-reading four files.
The rest of the layers
triggers, stop, reserved and
record are unions. description and goal take the
child’s value when it has one. brief and procedure
concatenate, baseline first — general rules, then the specifics that qualify them.
budget merges per key and a numeric limit takes the minimum: a
ceiling only ever comes down.
Baselines are marked abstract: true — real policy, but nothing routes to
them and they never appear as available work. docket new baseline --template
org-baseline ships a starting one. A missing baseline is an error, never a
silently ungoverned loop.
The record
Every warrant check and every piece of finished work is appended to
.docket/record.jsonl — one JSON object per line, append-only. Three
entry kinds:
| Kind | What it captures |
|---|---|
| check | a warrant check with its verdict and the rule that fired — written automatically. “Did the agent even ask?” becomes a grep. |
| note | work evidence: saw, did, skipped, stopped, note |
| amend | a human-approved warrant widening from docket review — rule changes are evidence too |
Who wrote it
Every entry is stamped with its subject, so several agents working the same repo in parallel worktrees stay separable at merge time:
$ docket record log #7 2026-07-24 09:12Z deploy allow change → "src/api/rates.ts" (change: source files) ← claude-code @ hotfix-402 #9 2026-07-24 09:14Z deploy allow read → "test/rates.test.ts" (read: the repo) ← cursor @ wt-perf:perf-pass
| Field | Where it comes from |
|---|---|
by | --by <agent>, then DOCKET_BY, then a detected agent (Claude Code, Cursor, Gemini CLI, Codex, Aider, GitHub Actions), then user:<name> |
branch | the git branch at write time (a short sha when HEAD is detached) |
worktree | the linked worktree’s name, when the write came from one |
session | the harness’s session id, when it supplies one — the Claude Code hook does |
Read it back with docket record log --by <agent> or
docket metrics --by <agent>. Fields with no honest value are omitted
rather than filled with a placeholder, and entries written before attribution existed
read as unattributed — never guessed at.
Attribution is self-reported, and cannot be revised. A process that can
write to the record can claim any by it likes — this is provenance, not
authentication. But by is inside the hashed entry, so rewriting who an
old entry blames breaks the chain at that entry. Whoever wrote it is held to what
they claimed at the time.
Parallel writers
Appending is read-the-head-then-chain-to-it, so appends are serialized across
processes with an exclusive lock file next to the record (stale locks broken after
10s; an error rather than an unlocked write if it can’t acquire one). Without that,
two agents writing at once both chain to the same entry and verify
reports tampering nobody did — and a false alarm in an audit trail is worse than no
alarm. Scope is one machine; a record on a shared network filesystem is outside what
this guarantees.
The hash chain
Each entry commits to the previous one:
hash = sha256(prev + canonical(entry)). Any edit, deletion, insertion, or
reordering breaks the chain at a specific entry, and
docket record verify names it.
One case a chain alone can’t see: cutting off the tail leaves a valid
shorter chain. So verify prints the current head hash — pin it somewhere
the log can’t reach (a password manager, a commit message, another machine), then
docket record verify --head <hash> detects truncation too.
Signing the head
Pinning the head catches truncation on one machine. Signing it makes the proof portable — something you hand to a client, an auditor, or a release.
$ docket record keygen # ed25519, kept outside the repo $ docket record sign ✓ signed the record at 47 entries → .docket/attestations/000047-fd4394fc8cd4.json $ docket record verify --attest --key <public key> ✓ record matches the attestation — signed at 47 entries, 12 appended since key: pinned — this is the key you said to trust
Verification checks the entry at the attested sequence number, not just the current head — so cutting ten entries and appending three more does not hide it. The log growing after a signature is normal and reported as such; the log diverging below the signed point is not.
sign will not do it. And an attestation
carrying its own public key proves only that the attestation was not edited —
anyone can generate a key and sign anything. Without --key,
verify says exactly that instead of printing a green check that means less
than it looks like.Review: iteration with a human veto
The record already knows where the warrant chafes: every unlisted action the agent
was stopped on is a logged default-ask. docket review clusters the
repeats and proposes the exact amendments:
$ docket review
2 proposed amendments — from repeated asks in the record
1. appeal — allow read: "state insurance regulations" (asked 4×)
2. appeal — allow draft: "timeline summary" (asked 2×)
allow read: "state insurance regulations" in appeal? [y/N] y
✓ appeal: read now covers "state insurance regulations"Three guardrails keep the automation honest:
- Applying is always a human keystroke. An agent that widens its own permissions is the exact failure docket exists to prevent — it’s a scenario in the red-team suite.
- Deliberate policy is never proposed. Targets matching the loop’s
askorneverlists don’t appear, however often they recur. - Every approved amendment lands on the record as an
amendentry — the evolution of your rules is itself auditable.
Flags: --min N (repeat threshold, default 2), --loop name
(one loop), --yes (apply all — for when you run it in a script
or cron). After approving, recompile so your tools see the new warrant.
Metrics: your autonomy posture, from the record
Because every check carries its verdict, the record is enough to report how much
the agent runs on its own versus stops for a human — no new data collected.
docket metrics reads it back:
$ docket metrics Warrant checks 7 allow 4 ██████████░░░░░░░░ 57% ran on its own ask 2 █████░░░░░░░░░░░░░ 29% stopped for a human deny 1 ███░░░░░░░░░░░░░░░ 14% hard stop Autonomy actions per intervention 2.3 proxy — checks ÷ (asks + denies) longest unattended run 3 consecutive allows, no human stop warrant amendments 0 human-approved widenings (docket review)
It reports the auto-approve / ask / deny split, the longest unattended run
(consecutive allows with no human stop), a proxy for actions-per-intervention, and a
per-loop, per-channel, and per-agent breakdown. Every number is exact from the hash chain except
the labeled proxies — the record measures actions and verdicts, not wall-clock or
intent. This is the dashboard Addy Osmani argues teams need to climb the autonomy
ladder deliberately toward “calibrated autonomy.” Add --json to
gate CI on it, or --loop <name> / --by <agent> to scope it — with several
agents on one repo, “how autonomous is this agent?” is the question that has an
actionable answer.
Compiling to your tools
Loops are the source of truth; assistant context files are build artifacts:
| Target | Writes | Read by |
|---|---|---|
claude | CLAUDE.md | Claude Code |
agents | AGENTS.md | ChatGPT/Codex, OpenClaw, Hermes, Zed, … |
gemini | GEMINI.md | Gemini CLI |
cursor | .cursor/rules/docket.mdc | Cursor |
raw | stdout | anything else |
Docket writes between <!-- docket:begin --> /
<!-- docket:end --> markers and manages only that block —
your hand-written content in the same file is never touched. Recompiling is
idempotent; loop content that quotes the markers is neutralized so it can’t corrupt
the block; an orphaned marker is an error, never silent data loss.
Because every target renders from the same loops, moving to a new tool is a recompile, not a re-teach.
Routing & context scale
The full compile puts every brief and procedure in the agent’s context on every turn. That stops scaling around a handful of loops — the rules start crowding out the work. The fix is architectural: rules scale on disk, not in context.
$ docket compile --index --target claude --write
✓ compiled index of 23 loops → CLAUDE.md--index compiles the same managed block in tiers instead of in
full:
| Tier | Resident? | Grows with |
|---|---|---|
| protocol | always | nothing — find the loop, load it, check the warrant, ask when uncovered |
| index | always | one line per loop: name, description, triggers |
| active loop | on demand | only the task at hand — docket compile --loop <name> or docket_loop_context |
| enforcement | never | the warrant check runs outside the model; its verdict carries the one matched rule into the conversation exactly when it matters |
docket compile prints a token estimate and suggests
--index when the full render grows past a few thousand tokens.
The two modes share markers, so switching replaces the block instead of stacking
a second one.
Which loop covers this task?
With only an index resident, something has to answer the routing question — deterministically:
$ docket match "draft an appeal for my denied claim"
1 candidate loop for "draft an appeal for my denied claim"
appeal Build the appeal, cite the policy — stop before send.
score 14 — name: appeal · trigger: denied claim, denial letter
$ docket match "wire funds to a vendor"
NO LOOP "wire funds to a vendor"
No loop covers this task. Work outside a loop defaults to askScoring is lexical and integer-weighted, reusing the warrant’s cautious matcher: the loop name read as a phrase (+5), each author-written trigger (+4), matching warrant targets (+1 each, capped), shared description words (+1 each, capped). Candidates need a score of 3; the top few are returned for the agent — or you — to make the final pick. One shared word like “email” never routes on its own.
Two rules matter more than the weights:
- The asymmetry inverts at routing time. The warrant matches allow-entries strictly because a false allow is an incident. Routing matches generously because a false candidate costs one extra index line — and a routing miss is still caught downstream by the warrant.
- Retrieval fails closed. No match doesn’t mean “best guess” — it means
no loop covers this task, ask the human.
docket matchexits 2 on no coverage, the same exit as an ask verdict, so hooks can gate on it.
The MCP server
docket mcp is a zero-config MCP server (stdio, newline-delimited
JSON-RPC). MCP hosts often launch servers with a working directory far from your
project, so pass --dir /path/to/project (or set
DOCKET_DIR); the server always answers initialize and
reports a missing project as a tool error instead of dying silently.
| Tool | What the agent gets |
|---|---|
docket_list_loops | discover the loops you’ve defined |
docket_match_loop | route a task (in plain words) to the loop that covers it — ranked candidates with why, or a fail-closed “no loop covers this, ask” |
docket_loop_context | a loop’s five layers, before starting work |
docket_warrant_check | allow / ask / deny for a proposed action — auto-logged, with explicit STOP instructions on ask/deny |
docket_record | append evidence: saw / did / skipped / stopped / note |
Checks the agent makes over MCP land on the record with via: "mcp" —
distinguishable from human CLI checks forever.
Hooks: hard enforcement
Compiled context tells the agent the rules; MCP makes checking cheap. For the tool
calls you actually fear, make the warrant mechanical: docket hook
claude plugs into Claude Code’s permission system as a PreToolUse hook. Add to
.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{ "matcher": "Bash|Write|Edit",
"hooks": [{ "type": "command", "command": "npx docket-agent hook claude" }] }
]
}
}Every matched tool call is piped through the warrant before it runs:
| Verdict | What Claude Code does |
|---|---|
| deny | the call is blocked; the loop, rule, and reason go back to the model |
| ask | Claude Code prompts you before the call runs |
| allow | silence. Docket emits no decision on allow — it only ever tightens the gate, never bypasses Claude Code’s own permission prompts |
How calls map to the warrant
- Read-shaped tools (
Read,Grep,WebFetch, …) check asread; file-writing tools (Write,Edit, …) aschange. Everything else —Bash, MCP tools, tools that don’t exist yet — checks assend, the most consequential verb, so unknown tools fall toward ask, never toward allow. - The target is the human part of the input (
Bash: git push origin main,Write: appeal-letter.md), so your warrant patterns match it in plain words. - Without
--loop, each call is routed with the same scoring asdocket match; when no loop claims the call, the hook stays silent and Claude Code’s own permissions decide. Pin one loop with--loop <name>, or add--strictto turn no-coverage into an ask. - Every check lands on the record with
via: "hook"— enforcement and evidence in one move. A bare hook (no--loop, no--strict) in a project without a.docketdirectory costs nothing. But once you pin a gate —--loopor--strict— every failure mode (a bad loop name, non-JSON stdin, a missing project) fails closed to ask: a gate you asked for never fails open.
The MCP gateway gate
The hook gates one harness. It works well and it works only in Claude Code — which is fine until you notice where an agent’s most consequential actions actually go. Not through the harness’s file tools: out over MCP, to a mail server, a ticket tracker, a cloud API, a payments provider.
docket intercept gates that side. Wired into the
Docker MCP Gateway as an
interceptor, every tools/call from any client to any server is checked
against the warrant before it runs.
docker mcp gateway run \ --interceptor 'before:exec:docket intercept --loop deploy' # or, with no node on the host: docker mcp gateway run \ --interceptor 'before:docker:ghcr.io/shahcolate/docket-agent intercept --loop deploy'
The gateway already intercepts what a gateway can know generically — secrets in payloads, OAuth failures. Docket adds what it cannot: whether this action was permitted, for this job, by the person accountable for it. A gateway can tell that a payload contains an API key. Only the warrant can tell the authorized appeal email from the unauthorized one.
ask blocks — it does not ask
A gateway has no human on the connection to prompt. At a PreToolUse hook,
ask raises a prompt someone can approve; here there is nobody, so the call
does not run and the message tells the model to get approval out of band. That is
strictly tighter than the hook — the only direction docket is allowed to differ
across surfaces — but it means a gateway-fronted workflow stops dead on anything
unlisted rather than waiting for a yes. Write the warrant knowing that.
Why silence is the dangerous case
The gateway’s contract inverts the hook’s: empty stdout means the tool runs.
So an interceptor that crashes, prints a warning to stdout, or emits anything the
gateway cannot unmarshal has allowed the call. All three are counted as
fail-open in the eval rather than filed as polish — 20 hostile tool calls (mail, PR
merges, refunds, DROP TABLE, an S3 wipe, a planted git hook, a wire
transfer, padding attacks against the target’s length cap), 0 allowed, 0 crashes.
Verbs
Every tool behind a gateway is third-party and arbitrary, so docket does not guess a
verb from a tool name — search_and_purge reads like a read. Everything
defaults to send, the verb whose allow list loop authors keep shortest on
purpose. --action read overrides that for a gateway you know fronts
read-only servers; it is a real widening and the docs say so.
Every gated call lands on the record with via: "gateway", so
docket review surfaces the asks you keep hitting. Full setup, including a
runnable Compose file, is in
examples/mcp-gateway;
how it composes with sandboxes is in
docs/sandboxes.md.
Sharing policy via a registry
extends: makes one file govern forty loops. It does not help when
the forty loops live in forty repositories. For that, put the policy in the registry
the team already runs.
$ docket policy push ghcr.io/acme/loops:baseline pushing 1 loop → ghcr.io/acme/loops:baseline 6b6359544a5a org-baseline.loop.md ✓ pushed ghcr.io/acme/loops:baseline digest: sha256:e568d3f76395…
Each loop is its own layer, annotated with its filename, so it stays individually
addressable, readable in any registry UI, and retrievable by generic tools like
oras. Registries already provide what policy distribution needs and a git
submodule does not: immutable digests, tags that move deliberately, per-namespace
access control, and a retention policy somebody else operates.
Pulling is the dangerous direction
A loop file is not data — it is the artifact that decides what your agents may do.
Fetching one from a network and dropping it into .docket/loops/ is
structurally the exact failure docket exists to prevent, wearing a supply chain as a
disguise. So pull shows what changes and then asks:
$ docket policy pull ghcr.io/acme/loops:baseline
ghcr.io/acme/loops:baseline
digest sha256:e568d3f76395…
+ new org-baseline.loop.md (abstract)
never 6 · ask 4
install 1 loop file into .docket/loops? [y/N]Digests are verified before a byte is written. A publisher-chosen filename is
validated against the loop-name grammar — a layer titled
../../../etc/cron.d/evil.loop.md is refused, and that is a test, not an
intention. An existing loop is never silently replaced. What was installed lands on
the record with its digest. docket policy inspect reads a policy back,
including every never rule, without installing anything.
Why extends cannot name a registry
So distribution is a separate, explicit, human step. Policy is pulled, vendored,
and committed; the warrant is always evaluated from local files a reviewer can read in
a diff. docket policy pull is a supply-chain step, not a runtime
dependency.
Per-tool setup
The pattern is the same everywhere: init → new →
compile into the file that tool already reads. Sixty seconds each.
Claude Code
npx docket-agent init npx docket-agent new followup --template client-follow-up npx docket-agent compile --target claude --write # optional — native checks + record via MCP: claude mcp add docket -- npx docket-agent mcp # optional — hard enforcement: wire `docket hook claude` into # .claude/settings.json as a PreToolUse hook (see “Hooks” above)
ChatGPT · Codex
npx docket-agent compile --target agents --write # → AGENTS.mdAGENTS.md is an open convention — the same compile covers Zed, Amp, and other tools that read it.
Gemini CLI
npx docket-agent compile --target gemini --write # → GEMINI.mdGemini CLI loads GEMINI.md as project context on launch. Running Claude and Gemini side by side? Compile both targets — one rule file governs both.
Cursor
npx docket-agent compile --target cursor --write # → .cursor/rules/docket.mdcOpenClaw
OpenClaw injects the workspace’s AGENTS.md into the system prompt at
the start of every session — so run docket inside the workspace:
cd ~/.openclaw/workspace npx docket-agent init npx docket-agent new followup --template client-follow-up npx docket-agent compile --target agents --write
Docket manages only its own marked block — your existing AGENTS.md rules,
SOUL.md, and the rest of the workspace stay untouched. For native checks, add docket
as an MCP server in your OpenClaw config:
command: npx, args: ["-y", "docket-agent", "mcp", "--dir",
"~/.openclaw/workspace"].
Hermes (Nous Research)
Hermes reads AGENTS.md context files — run the same three commands in
the directory Hermes works from, then optionally register the MCP server in
~/.hermes/config.yaml under your MCP servers section:
docket: command: npx args: ["-y", "docket-agent", "mcp", "--dir", "/path/to/your/project"]
Hermes builds skills from experience; docket keeps the experience inside the warrant, on the record.
Any other MCP client
{ "mcpServers": { "docket": {
"command": "npx",
"args": ["-y", "docket-agent", "mcp", "--dir", "/path/to/project"] } } }CLI reference
| Command | Does |
|---|---|
docket init | create .docket/ here (--dir, --quiet) |
docket install | make docket ambient in the repo: compile context for every agent, wire the Claude Code PreToolUse hook, add MCP config (--loop, --strict, --no-hook, --no-mcp, --targets); merge-safe and idempotent |
docket new <name> | create a loop — interactive five-question interview on a TTY, --template <t> to start from a starter, --blank for a scaffold |
docket templates | list the nine starter loops |
docket list | list your loops |
docket show <loop> | print a loop’s five layers |
docket match <task…> | which loop covers this task? — ranked with why (--limit N); exits 0 matched / 2 no coverage |
docket check <loop> <verb> <target…> | verdict + rule; exits 0/2/3; logged unless --no-record; --quiet prints the bare verdict; --by <agent> names the writer |
docket record add <loop> | append evidence: --saw --did --skipped --stopped --note; --by <agent> names the writer |
docket record log [loop] | show recent entries (--n 20), each with who wrote it; --by <agent> shows one agent’s |
docket record verify | verify the chain end to end; --head <hash> pins against tail truncation; --attest [file] checks it against a signed head, --key <pub> pins who signed |
docket record keygen | generate an ed25519 signing key, mode 0600, kept outside the repo (--out, --force) |
docket record sign | sign the current head into .docket/attestations/ (--key, --note, --json); refuses to sign a chain that does not verify |
docket metrics | autonomy posture from the record: auto-approve / ask / deny split, longest unattended run, actions per intervention, per-loop, per-channel & per-agent (--loop, --by, --json) |
docket review | propose warrant updates from repeated asks (--min, --loop, --yes) |
docket compile | --target claude|agents|gemini|cursor|raw, --write to update the file in place, --loop to preview one loop on stdout, --index to compile the routing table instead of full loops |
docket mcp | run the MCP server (--dir or DOCKET_DIR to locate the project; --by names the client in the record) |
docket hook claude | Claude Code PreToolUse hook: deny blocks, ask prompts the human, allow stays silent (--loop pins a loop, --strict asks on no coverage, --dir locates the project, --by names the writer); with a gate pinned, every failure mode fails closed to ask |
docket policy push <ref> | publish loops as an OCI artifact (--loop a,b to select a subset); refuses to publish a loop that does not parse |
docket policy pull <ref> | preview, confirm, then install (--yes, --force, --dry-run); verifies digests, refuses publisher-chosen paths, records what it installed |
docket policy inspect <ref> | read a policy back — every loop and every never rule — without installing it (--json) |
docket intercept | Docker MCP Gateway interceptor: gate every tools/call through the warrant, whatever the client or server (--loop, --strict, --action <verb>, --server, --dir, --by). A gateway has no human to prompt, so ask blocks rather than asks |
The red-team eval
The claim “fuzzy only toward caution” is tested, not asserted — 10,614 checks
across seven suites, all deterministic, all reproducible with npm run eval,
every invariant enforced in CI:
| Suite | Checks | Result |
|---|---|---|
| Behavior scenarios — real agent-overreach incidents, incl. scheduled escapes | 61 | 0 silent allows · 21/21 warranted work allowed |
| Adversarial phrasing — euphemism, compound intent, injection, homoglyphs | 42 | 42/42 contained |
| Vague-target probes — every proper subset of every allow pattern | 218 | 0 permissions inherited |
| Fuzzed targets — seeded, vocabulary disjoint from every allow entry | 10,000 | 0 allowed |
| Record tampering — edits, rehashes, deletions, reorders, forgeries, truncations | 239 | 239/239 detected with pinned head; 197 by the chain alone |
| Hook gate — the live binary vs hostile PreToolUse payloads | 24 | 14 hostile calls, 0 allowed · 3/3 benign allowed · 0 fail-open |
| Gateway gate — the live binary vs hostile MCP tool calls | 30 | 20 hostile calls, 0 allowed · 3/3 benign allowed · 0 fail-open |
Zero silent allows. Zero fail-open outcomes. Zero warranted work blocked.
Where a paraphrase weakens a hard stop, it weakens to ask — never to allow,
and the report says so in the open. If any invariant regresses, the build fails
(test/scenarios.test.js, adversarial, properties,
tamper, hookgate, gatewaygate). The full tables are in
eval/REPORT.md.
FAQ
Does docket execute anything?
No. It describes and constrains work; your agent does the work. That’s deliberate — it means docket works with every agent, including ones that don’t exist yet.
What stops the agent from just ignoring the rules?
Three layers, in increasing strength: the compiled context tells it the rules
(models follow explicit written boundaries far better than vibes); the MCP tools make
checking cheap and logged, so skipping the check is visible in the record; and
docket hook claude makes it mechanical —
Claude Code pipes tool calls through the warrant before they run, blocking on deny
and prompting you on ask. On other agents, the exit codes give you the same lever:
wrap risky tools in a shell guard that calls docket check and refuses on
exit 2/3. Advisory for cooperative agents, enforceable where it counts.
Why not just a sandbox?
Use one — they’re complementary layers. A sandbox bounds damage: what the
process can physically reach. Docket bounds authority: what the agent was
allowed to do, and whether you can prove what it did. A sandbox can’t tell the
authorized email from the unauthorized one — both are legitimate traffic through the
proxy. And the failure that connects the layers is the scheduled escape: an action
that looks contained inside the session and detonates after it (a planted git hook, a
queued send). That’s a scenario family in the eval, a never in the
templates, and a rule in the spec.
What happens when no loop covers the task?
Docket never invents a verdict for work you didn’t write a loop for — retrieval
fails open to the human, the warrant fails closed. What “no loop” means depends on
where you are: the hook by default is pass-through (silent,
your normal permission flow decides), or under --strict it asks;
inside a matched loop an unlisted action is ask (the default verdict —
silence is never permission); and docket match exits 2 so
scripts and CI can branch on “unrouted.” Docket only governs what you wrote loops for;
it doesn’t seize unowned work.
How do I keep docket out of something?
It never executes anything — it only returns a verdict and appends to the record, so
there’s nothing to switch off mid-action. Engagement is opt-in per surface: don’t wire
the hook and docket only answers when explicitly asked (docket check / the
MCP tools); scope the hook’s matcher to the tools you actually want gated;
and outside a .docket project a bare hook costs nothing.
Can the record be faked?
Edits, deletions, insertions, and reordering break the hash chain at a named
entry. Tail truncation is caught by pinning the head hash
(verify --head). What a local file can’t prove is authorship — signing
the head is on the roadmap.
What goes in a loop vs. what stays out?
In: the context that changes answers, standards, hard limits. Out: secrets, tokens,
passwords — loop files are meant to be committed and shared. The
cross-tool-memory template marks storing secrets as never.
Multiple people, one repo?
Commit .docket/ and the compiled files. Everyone who clones inherits
the same warrant, and every agent working the repo is under the same rules.
What if two patterns conflict?
Order decides: never beats ask beats allow, and unlisted always asks. A target matching both an allow and an ask pattern asks.