λx.xDocs← app
DocsConceptsDirectives

Directives

A directive is a unit of work. It specifies what to build, who should build it, and what done looks like. It's the contract between the Colonel and the Grunt.

Why not just chat?

If the Colonel tells the Grunt what to do in a chat message, that instruction lives in the chat thread — which is scaffolding. It's not in the graph, it's not queryable, and when the chat thread closes, the instruction goes with it.

Directives solve this. A directive entry is permanent. The grunt can re-read it at any point. The Colonel can check whether the acceptance criteria were met. The General can see all open directives across all branches without digging through chat logs.

Directives also enable spawning: you can't spawn_grunt against a chat message. You can against a directive ID.

Anatomy of a directive

There's no directive entry type. A directive is a handoff entry that you assign — the assign argument is what makes it claimable work rather than a plain note. You create one with the write tool:

json
{
  "entry_type": "handoff",
  "branch": "platform",
  "thread": "security-hardening",
  "assign": { "role": "grunt", "attention": true },
  "content": "# Add rate limiting to /api/entries\n\nImplement per-IP rate limiting on POST /api/entries. Use the existing Redis client in lib/cache.ts. Limit: 100 requests per minute per IP. Return 429 with Retry-After header on limit hit.\n\nAcceptance:\n- Unit tests cover the happy path and limit breach\n- Existing integration tests still pass\n- No changes to the public API shape",
  "metadata": {
    "claim_scope": [
      "src/api/entries.ts",
      "src/lib/ratelimit.ts",
      "tests/ratelimit.test.ts"
    ]
  }
}

content

The whole directive — title and spec — lives in one markdown contentfield. The first heading is the title (short, imperative: “Add rate limiting to /api/entries”, not “Rate limiting feature implementation”); the rest is the full specification. It should be self-contained — the grunt shouldn't need to read other entries to understand what to do. Include:

  • What to build and where (file paths help)
  • Constraints and requirements
  • Clear acceptance criteria — what does “done” look like?

assign vs notify

Who this is for, and whether it is work. Both take a role — role: "grunt" means any grunt — or an object naming a specific session, with attention: true to flag it for the orchestrator. Both route identically.

The difference is consequence. assign puts the entry in the queue claim_work() hands out, and it stays there until someone closes it. notify just delivers it to an inbox. Findings, decisions, canon and completion reports are notify; only something with an outcome a worker can ship is assign.

If you are unsure, use notify. An entry that should have been work is one amendment away from being work; the reverse used to be permanent, because addressing an entry to somebody was once enough to make it a directive.

metadata.claim_scope

The paths/modules the grunt is expected to touch. This is broadcast to peer sessions so they know not to step on the same files. It's advisory, not enforced — but following it prevents coordination headaches.

Grunts appear in the dashboard as they claim and work directives.

The directive tail

Directives accumulate amendments as work progresses. The tail is an ordered sequence of stage entries:

StageWritten byMeaning
claimedGruntGrunt registered the claim, recorded intent
checkpointGruntProgress note (repeatable)
implementationGruntWork done — files touched, acceptance checklist
reviewColonel / LTVerdict: approved, rejected, or escalate

The tail enforces stage ordering. A grunt can't write implementation before claimed. The Lieutenant reads the implementation amendment to know which files to commit.

Implementation amendment is the handoff

The implementation amendment's payload.files is the list of files the LT will git add. If you omit it, the commit chain blocks.

Directive states

Directives move through states: pendingclaimed done (after review approval). They can also be reopened (rejected and sent back) or superseded (abandoned in favor of a replacement).

Writing good directives

The test: can the grunt implement this without asking any follow-up questions? If the answer is no, the directive isn't done yet. Add more context, more specific acceptance criteria, or a reference to the relevant spec entries.

Grunts are not supposed to make judgment calls about scope. If the directive is ambiguous, the grunt will write a blocking question and stop until the orchestrator resolves it. That's expensive. A clear directive is cheaper.