neuron-explorer
Qwen3.5‑4B 전용 조향형 뉴런 탐색기, 모든 MLP 뉴런을 밀어 결과를 보관·탐색
세션은 일시적이지만 작업 상태는 지속, Director는 에이전트가 기록하는 원장으로 새 세션에 주입
Sessions are disposable. The state of the work isn't. Director is a ledger your agent writes as it works, injected into every new session as ground truth.
개발 도구Go · ShellGitHub @colinsurprenant온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
Coordination ledger for coding agents: what was decided, what's still open, where you parked — durable across sessions, repos, and weeks. Memory tools store what the agent knows; Director tracks the state of the work. Run both: they don't overlap. Claude Code + Codex + OpenCode.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Save the work, not the chat.
One shared log across Claude Code · Codex · OpenCode · Copilot CLI: build in one, resume or review in another.
The session is long past its best. You keep pushing it anyway, because it holds everything you accumulated on the way here: the decisions, the dead ends, the loose ends, where you were going.
Director removes the fear of losing that context: your agent records the state of the work (decisions, open loops, handoffs) to a small local log as it goes, and every new session starts with the log loaded:README 전문 보기 ↗Save the work, not the chat.
One shared log across Claude Code · Codex · OpenCode · Copilot CLI: build in one, resume or review in another.
The two-minute tour: colinsurprenant.github.io/director
The session is long past its best. You keep pushing it anyway, because it holds everything you accumulated on the way here: the decisions, the dead ends, the loose ends, where you were going.
Director removes the fear of losing that context: your agent records the state of the work (decisions, open loops, handoffs) to a small local log as it goes, and every new session starts with the log loaded:
$ claude
… deep into the pagination rework …
▸ decision recorded · cursor pagination, not offset; offsets break under deletes
…
▸ open-item recorded · timezone edge case before the backfill merges
… later …
▸ handoff parked · cursor rework done · next: the backfill script · watch the p99
──────────── session ends ────────────
$ codex
> where were we?
▸ Director: acme-api-main-7c21e9d4 · 1 open-item(s), 1 need-you
## open-items
01KWJ4X2… open-item timezone edge case before the backfill merges [risk:escalate]
## handoffs
01KWJ9RM… handoff cursor rework done · next: the backfill script · watch the p99
## decisions
01KWJ4W8… decision cursor pagination, not offset; offsets break under deletes
Recorded while it works; loaded when the next one starts.
Sessions are disposable. The state of the work isn't. /clear the moment a session degrades. Park a repo, come back whenever. The state of the work lives in the log, not in the chat.
Concurrent sessions, one ledger. A builder in Claude Code, a reviewer in Codex, OpenCode, or Copilot CLI, a third session in a parallel worktree: all writing the same log at once, losing nothing (see One ledger, four harnesses).
Three things it is not:
Under the hood, deliberately boring:
Install is one line (macOS / Linux / WSL); it wires Claude Code:
curl -fsSL https://raw.githubusercontent.com/colinsurprenant/director/main/install.sh | sh
Wire Codex instead with … | sh -s -- --codex, OpenCode with --opencode, GitHub Copilot CLI with --copilot, all four with --all (flags combine; see Install).
Scope: single-machine for now, single-human by design; multi-machine sync is on the roadmap (see Status & scope).
New here?
docs/getting-started.mdis the task-oriented first-run guide (install → adopt → first session → cockpit), plus how the model uses Director and a troubleshooting section. This README is the reference.
The session boundary is where the state leaks: a reset, a compaction, a session ending, a week away, a parallel worktree. Director sits at that boundary and makes the reset free, and you don't operate it: it wires into Claude Code, Codex, OpenCode, and Copilot CLI through hooks, the session emits as it works, and that state is injected into the next one as ground truth. It moves you out of the message bus seat: the ledger carries the state between sessions, and you go back to directing the work. Built around a shared, durable, append-only event log per repo:
emit typed events as they work (decision · open-item · handoff · note) and resolve open loops when they truly close.render (the machine digest), brief (the human re-orientation view), and status (the one-line-per-workstream cockpit).When several things are in flight, director status is the whole board at a glance:
acme-api-main-7c21e9d4 · active · just now · blocked(1): timezone edge case before the backfill merges
billing-worker-main-3f8a1c2d · idle · 6h ago · ok
docs-site-main-9d2e5b71 · dormant · 13d ago · ok
One human, many workstreams: which are live, which are parked between blocks, and the one line blocked on a human call.
Memory tools answer "what does the agent know?" Director answers "what is the state of the work?": what was decided and why, which loops were deliberately deferred, and what still needs you. Facts accumulate; loops open and close, and nothing in a memory store ever closes. That lifecycle is the difference, and so is the delivery: pushed at session start, not recalled by similarity. Run both: they don't overlap.
The LOG (plus the deliberately-edited living docs) is the only system of record; sessions and every rendered view are disposable caches reconstructible from it. Director wires natively into Claude Code, OpenAI Codex, OpenCode, and GitHub Copilot CLI: same log, same boundary commands, any of them alone or side by side. Harness-level wiring also means model-agnostic: sessions pointed at a compatible third-party endpoint (GLM via z.ai, OpenRouter, a local model) coordinate identically. A single static binary, stdlib-first, one vetted build-time dependency (github.com/oklog/ulid/v2). No daemon, no database, no cloud, no telemetry: the binary never opens a network connection, and the log is plain NDJSON.
Any number of sessions, in any mix of harnesses, share the same Director log through an append-only writer that loses nothing under concurrency on a local filesystem. Parallel worktrees see each other's decisions instead of clobbering them; a reviewer in another tool lands its verdict in the same log the builder reads. In practice:
note for the next session to pick up. Different vendors, different models, one state of the work.Wiring is symmetric: director install --all (or any combination of --codex / --opencode / --copilot with the bare Claude Code default) gives every harness the same boundary commands and the same ground-truth injection at the top of every session. Details per agent are under Install.
The design in four ideas (the full argument, including honest comparisons, is docs/why-director.md):
brief + status. If a session dying loses real information, that's a liability, not an architecture.How it compares, in one line each (full versions): compaction compresses the transcript at an arbitrary moment and hopes the right things survive (a rot vector, not a cure) while Director records the work itself, typed facts written when they happen that outlive the session; memory tools answer "what does the agent know?" while Director answers "what is in flight?"; issue trackers (beads et al.) hold the work items while Director holds the narrative between tasks; native multi-session features (Agent Teams) are session-scoped by design while Director is the durable, git-adjacent layer underneath them; and versus a markdown file plus discipline, Director adds append-only integrity under concurrent writers, byte-identical verifiable views, resolve lifecycle semantics, and push-injection that doesn't depend on the model remembering to read a file.
One command downloads the right prebuilt binary for your platform (checksum-verified), installs it to ~/.local/bin, and wires it into Claude Code. Installed and wired, no second step (it tells you if ~/.local/bin isn't on your PATH yet):
curl -fsSL https://raw.githubusercontent.com/colinsurprenant/director/main/install.sh | sh
Wire Codex instead with … | sh -s -- --codex, OpenCode with --opencode, GitHub Copilot CLI with --copilot, all four agents with --all (wire flags combine: --codex --opencode wires exactly those two); install the binary only with … | sh -s -- --no-wire.
On Windows? Run the one-liner inside WSL with the Linux binary: everything works there, hooks included. Native Windows is CLI-only for now: the binary is built and CI-tested, and every manual verb (
emit,render,status,brief,show,resolve, …) works from PowerShell, but the hook shims are bash, so the ambient layer (session-start injection, heartbeats, boundary nudges) is not yet wired natively.
One machine for now. Director's hub lives on the machine you install it on; there is no cross-machine sync yet. Work a repo from two machines (a laptop and a desktop, say) and each keeps its own separate hub: neither sees the other's decisions, open loops, or handoffs. Multi-machine sync is the one distribution mode on the roadmap (see Status & scope); until then, drive a given repo's Director state from a single machine.
Other ways in: prebuilt binaries for macOS, Linux, and Windows (amd64/arm64) are on the releases page (docs/getting-started.md covers install-from-release), go install github.com/colinsurprenant/director/cmd/director@latest builds from source (Go 1.25+), or build it yourself:
go build -o bin/director ./cmd/director
sudo install bin/director /usr/local/bin/director # or copy it anywhere on PATH
Then wire it into your agent. The one-liner already wired Claude Code; the sections below spell out what that does, and how to add Codex, OpenCode, and Copilot CLI. The target flags combine (--codex --opencode wires exactly those two) and director install --all wires all four in one go. When you upgrade, re-run director install (the one-liner does it for you): it refreshes the hook shims and the installed boundary commands, which a release may have changed.
director install
director install does four things, all idempotent and self-contained:
install materializes them (executable) into the hooks dir. There is no manual copy step.~/.claude/settings.json. Every entry it writes carries a "_managedBy":"director" tag, so Director's hooks run alongside GSD's and any hand-rolled hooks without clobbering them. Re-running adds nothing; director uninstall removes only Director's own entries (tagged, or untagged at its shim paths) and the shims and command files it wrote. Pass --settings <path> to target a project or test settings file./director:adopt, /director:complete, and /director:handoff commands (also embedded) are written under ~/.claude/commands/director/, namespaced so they never clobber a user's own commands.install adds the hub (~/.director, or your DIRECTOR_HUB) to sandbox.filesystem.allowWrite in settings.json, leaving any entries you added there untouched.The installed hook commands point at the shims, not the binary directly, so rebuilding or relocating director never requires rewriting settings.json (re-run install to refresh the shims to the current binary). If ~/.claude/settings.json already has a malformed (non-object) hooks value, install refuses rather than overwrite it.
Confirm the wiring will actually fire with director doctor. The shims fail safe (a missing binary exits 0 and coordination silently no-ops), so a broken install is otherwise invisible; doctor walks the same binary-resolution ladder the shims walk and reports each link (binary, Claude Code hooks, Codex, OpenCode, and Copilot CLI hooks if present, the hub's sandbox write grant, hub). It exits non-zero when the install is broken, and warns (without failing) on a partial one, such as a terminal-only install the desktop app would miss, or on a DIRECTOR_DISABLE left set:
director doctor
director install --codex
Codex's hook contract mirrors Claude Code's, so the same shims serve every command-hook agent (Claude Code, Codex, Copilot CLI), and no install needs another: --codex works standalone on a machine that has never run Claude Code. It merges the three hooks into ~/.codex/hooks.json (never your config.toml) and installs the boundary commands as agent skills under ~/.agents/skills, invoked as $director-adopt, $director-complete, $director-handoff. Codex asks you to trust the three hooks at your next session start (if you dismiss that prompt, run /hooks in the session). Details, including what degrades on Codex, in docs/getting-started.md.
Launch Codex with codex --add-dir <hub>/projects/<key> (<hub> is ~/.director by default, or your DIRECTOR_HUB value; <key> ends the first line of director render in the repo; the protocol line Codex receives at session start names the exact path) so emits don't stop on an approval prompt under Codex's default on-request approvals: it grants only the project's log dir. Don't add a director allow rule to Codex's execpolicy instead: a matched allow runs the command outside the sandbox, so any Codex agent, read-only reviewers included, could write the log. director doctor warns when Codex's own check (codex execpolicy check), run over your user rules dir ($CODEX_HOME/rules, else ~/.codex/rules), says director emit, director resolve or director promote is allowed; project-level .codex/rules and managed policy are not checked.
Everything below uses the Claude Code command names (/director:adopt etc.); on Codex and Copilot CLI, read each as its $director-* skill twin, and on OpenCode as its flat /director-* custom command (/director-adopt): same command, same behavior.
director install --opencode
OpenCode hooks are in-process plugin calls, not command hooks, so instead of shims the --opencode form drops one self-contained managed plugin at ~/.config/opencode/plugin/director.js (a pure file drop: OpenCode loads it with no registration, and no config file of yours is ever merged or modified) plus the boundary commands as custom commands at ~/.config/opencode/command/, invoked as /director-adopt, /director-complete, /director-handoff. Works standalone; nothing else is required. Ground truth injection (first message of each session on OpenCode 1, the system prompt of every model request on OpenCode 2; re-grounded after a compaction on both), liveness, and close-out work as on Claude Code; the Stop emit-guard and the context-fill handoff nudge read CC's transcript format and stay safely inert on OpenCode. Details in docs/getting-started.md.
Both OpenCode majors are supported from the one plugin file (verified live on OpenCode 1.18.3, 1.19.3, and 2.1.0). OpenCode 2 rejects the OpenCode 1 plugin shape and OpenCode 1 rejects the reverse, and both scan the same directory, so the file carries both entry points. If you upgraded OpenCode to 2 with an older Director plugin in place, Director silently stopped firing: re-run director install --opencode (director doctor flags it). One OpenCode 2 caveat: plugins run inside OpenCode's server, and by default every OpenCode 2 client shares one background server, spawned with the environment of whichever client starts it and keeping that environment until it restarts. DIRECTOR_DISABLE, DIRECTOR_HUB, DIRECTOR_BIN, and DIRECTOR_FLUSH_NUDGE_EVERY are read from that server's environment, so the hazard runs both ways: a value exported in one terminal does not reach a session served by an already-running service, and a DIRECTOR_DISABLE=1 or DIRECTOR_HUB=... on the opencode run that happens to start the service applies silently to every later session on the machine, which director doctor cannot see. On OpenCode 2, pair any per-run DIRECTOR_DISABLE or DIRECTOR_HUB with --standalone (opencode run --standalone, a private server that inherits your environment), or set it deliberately where the service is started.
director install --copilot
Copilot CLI speaks Claude Code's hook dialect once the events are registered under their PascalCase names, so the same shims serve it too, and it needs no other install: --copilot works standalone. It drops one Director-owned hooks file at ~/.copilot/hooks/director.json registering the four events (Copilot loads every JSON file in that directory, so no config file of yours is ever merged or modified, and there is no trust step: the hooks are live at the next session start). The boundary commands are the same agent skills the Codex install writes under ~/.agents/skills, invoked as $director-adopt, $director-complete, $director-handoff, and Copilot discovers that directory natively: installing either target provides them, and uninstalling one spares them while the other remains. Ground truth injection, liveness, and close-out work as on Claude Code, session-end row reaping included; the Stop emit-guard and the context-fill handoff nudge read CC's transcript format and stay safely inert. Details in docs/getting-started.md.
Install paths and runtime knobs, common to every agent unless a default says otherwise:
| Variable | Default | Selects |
|---|---|---|
DIRECTOR_SETTINGS_PATH | ~/.claude/settings.json | the Claude Code settings file install merges into (also the probe that decides whether an uninstall may reclaim the shared shims) |
DIRECTOR_HOOKS_DIR | ~/.claude/director/hooks | where install writes the shims and the settings entries point; override to relocate them |
DIRECTOR_COMMANDS_DIR | ~/.claude/commands/director | where install writes the /director:* slash commands |
DIRECTOR_CODEX_HOOKS_PATH | ~/.codex/hooks.json | the Codex hooks file install --codex merges into |
DIRECTOR_CODEX_SKILLS_DIR | ~/.agents/skills | where install --codex and install --copilot write the $director-* agent skills. One shared surface: both agents read this directory, either install provides it, and uninstalling one leaves it alone while the other remains |
DIRECTOR_OPENCODE_PLUGIN_PATH | ~/.config/opencode/plugin/director.js | the managed plugin file install --opencode drops |
DIRECTOR_OPENCODE_COMMANDS_DIR | ~/.config/opencode/command | where install --opencode writes the /director-* custom commands |
DIRECTOR_COPILOT_HOOKS_PATH | ~/.copilot/hooks/director.json | the Director-owned hooks file install --copilot writes (Copilot CLI loads every JSON file in that directory) |
DIRECTOR_HOOK_AGENT | (unset) | which agent a hook is running under. Not a knob to set: install --copilot writes DIRECTOR_HOOK_AGENT=copilot into each hook command it registers, because Copilot's payloads carry nothing that identifies them (no transcript path), and Copilot needs a different output shape than Claude Code. Exporting it globally in your shell mis-flavors the other harnesses' hooks, since this value outranks payload detection: leave it to the installer. fails the Copilot row if the prefix goes missing, which is otherwise a silent loss of ground-truth injection |
The binary must be findable. With
DIRECTOR_BINset, the shims use it and nothing else; a stale value exits 0 without ever trying the other tiers. Unset, they fall back todirectoronPATH, then to the symlinkinstalldrops next to them at<hooks dir>/../bin/director(~/.claude/director/bin/directorby default; aDIRECTOR_HOOKS_DIRoverride moves it too). A miss exits 0 (fail-safe) and coordination silently no-ops. The symlink tier covers the Claude Code desktop app, whose Dock/Launchpad launches get the bare launchdPATH(anthropics/claude-code#44649). To pin a specific binary explicitly, setDIRECTOR_BINvia"env"in~/.claude/settings.json.director doctorflags a staleDIRECTOR_BINthat resolves to nothing, the failure mode that silently disables the fallback tiers.
A director's projects already exist, so adoption of existing repos is on the critical path. It has two layers: director adopt registers; /director:adopt understands (on Codex and Copilot CLI: $director-adopt; on OpenCode: /director-adopt — same command, different delivery). From inside (or pointing at) a repo:
director adopt [<dir>] # defaults to the current directory
Working in an agent session, you can skip straight to /director:adopt (Claude Code), $director-adopt (Codex and Copilot CLI), or /director-adopt (OpenCode): the command runs this registration itself as its first step. The bare CLI verb is what you use outside a session (scripts, a quick shell registration).
Adoption requires a git repository: workstream identity and liveness are derived from git. On a non-git directory adopt fails fast and tells you to git init first (an empty init is enough).
adopt (the register layer) derives the repo's stable workstream identity (handling worktrees, remotes, and forks; see Identity), creates projects/<repo-key>/ in the hub, scaffolds a ~3-line CHARTER stub there, and registers the workstream in the fleet. Re-adopting never clobbers an edited CHARTER. That is all the CLI does: deterministic, done in seconds.
The understand layer is /director:adopt (Claude Code; $director-adopt on Codex and Copilot CLI; /director-adopt on OpenCode), installed by the matching director install form and run inside an agent session. It starts with the same director adopt, then fans out read-only agents over the repo (docs and planning files, code TODOs read in context, git state, the repo's self-descriptions) and brings back two things for your confirmation:
(inferred), plus the short list of questions only you can answer. Approved, it replaces the stub; adoption starts from an informed draft instead of a blank template.open-item events after your confirm; git state must corroborate the prose, which keeps this bucket naturally small), backlog (stays in the repo's own tracker and planning docs; Director is not the tracker), doc-stamps (facts wearing a TODO costume; they feed the CHARTER), and fossils. Every bucket's count is reported; nothing is imported silently.Re-running /director:adopt on an adopted repo is the refresh path: the proposal diffs against your current CHARTER, and triage dedupes against the log's existing open-set.
write path (model-emitted):
emit append a semantic event (decision|open-item|handoff|note)
resolve close an open-item by its ULID
promote fold decisions' rationale into a slow-layer doc
(promote <ulid>... --to <doc>; a doc pointer stays in the digest)
projections:
render deterministic machine digest (+ --json, --verify, manifest)
brief human re-orientation view (the bigger picture)
status one-line-per-workstream fleet cockpit
open-items a workstream's unresolved open-items (ULID + body), for /complete
(default: current workstream; --workstream <id> targets a sibling)
show one event in full by ULID — the pull path behind the digest's
capped headlines (+ --json; --project targets another project)
fleet lifecycle (hook-emitted):
register create/refresh this workstream's fleet row
heartbeat touch liveness
done archive this session's row (--workstream <id>: all of a sibling's rows)
adoption & install:
adopt register an existing repo (identity + CHARTER stub + fleet row)
install idempotent merge of Director hooks into settings.json
(--codex: Codex's hooks.json + $director-* agent skills;
--opencode: managed plugin + /director-* custom commands;
--copilot: Copilot's hooks file + $director-* agent skills;
targets combine, --all wires all four, bare = Claude Code)
uninstall remove only Director-managed hook entries (--codex / --opencode /
--copilot: theirs; targets combine, --all for all four)
doctor check the install is wired and the hooks will actually fire
misc:
version print the director version
emit is the only sanctioned way a semantic event reaches the LOG (never Edit/Write a log file):
director emit --type decision|open-item|handoff|note --area <subsystem> \
[--risk low|escalate] [--to <handle>] [--refs <ulid,ulid>] [--conclude <handoff-ulid,...>] - <<'DIRECTOR_EOF'
<body>
DIRECTOR_EOF
Every flag goes before the body (the - or a positional body): emit refuses its own flags placed after it (anything else there becomes body text), and refuses --refs or --conclude given twice (several ULIDs are one comma-separated list). The - reads the body from stdin; the quoted heredoc keeps $, backticks and quotes literal, which a double-quoted argument does not. (A plain <body> argument is still accepted; a body that is exactly - is reserved for stdin.)
emit prints the new event's ULID to stdout: note it; that is the id used to --refs or resolve the event later. (Two --refs pairings and one flag are load-bearing: a note's --conclude <handoff-ulid> (note only, and only a handoff that is still a live resume point) concludes that handoff (a note's --refs may not name a handoff: emit refuses it, so cite the ULID in the note's body instead; --conclude is solely the /director:complete close-out of a finished workstream), a handoff ref naming same-workstream handoff(s) supersedes exactly those resume points and no others, and a decision ref naming decision(s) supersedes them, retiring them from the digest's active decisions — see the kind table's lifecycle column below.)
emit also echoes a routing line to stderr (→ <repo-key> · <workstream-id>) naming the project it wrote to. If that is not the project the session expects, its cwd drifted and the event landed in the wrong log.
director resolve <ulid>
resolve appends a close-marker for an open-item. The <ulid> must be one the CLI surfaced (from emit, render, or open-items); resolve validates the target and rejects invented ids, non-open-items, and already-closed items.
director promote <ulid> [<ulid>...] --to <doc>
promote is the grooming ceremony that keeps the digest tracking current work, not project age. Once a batch of aged-but-durable decisions' rationale has been written into a living doc (an ADR, an architecture overview), promote appends one promote-marker: a decision with status: promoted, refs naming the promoted decisions, and promoted_to carrying the doc path. The fold that builds the projections drops the promoted decisions from the active view; the marker stays as a one-line doc pointer, and director show <ulid> still serves every original in full. Nothing is lost, the rationale just changed address (and since Director is single-human by design, promotion into the slow layer is also the cross-human interface).
Validation is resolve-parity: every target must be a decision the CLI surfaced, not superseded by an ordinary decision, and not already claimed by a live promote-marker. Invented ids, non-decisions, already-promoted and superseded targets are refused, and one bad target rejects the whole batch (nothing is written). A mispointed promotion is recoverable: supersede the bad marker with an ordinary decision (emit --refs <marker-ulid>), then re-promote to the correct address. Only live markers hold the already-promoted claim.
--to takes a durable address, not necessarily a file: a repo-relative path (docs/adr/0007-cursor-pagination.md) or a stable URL (a GitHub issue where the rationale now lives). Machine-specific paths (absolute, ~/…) are refused: the log is a portable file and its pointers must travel. Two conventions, nudged not gated: prefer the repo doc (it's version-controlled and travels with the repo; a URL can die, though the original rationale stays one show away), and have the receiving doc cite the promoted ULIDs, so a reader can drill back from the ADR into the full decision chain, superseded alternatives included. Director records the address; it never dials it: no issue is created, no doc is checked, the write-the-doc-then-promote ordering is yours.
| Command | Audience | What it is |
|---|---|---|
render [--project <key>] [--verify] [--json] | machines / hooks | the deterministic digest a session-start injects; --json returns the same live projection as versioned JSON with complete event bodies; --verify re-folds and asserts the selected output is byte-identical, exiting non-zero on drift. Also writes a manifest under health/. |
brief [--project <key>] | human | the on-demand bigger-picture re-orientation view (outlook from CHARTER, the resume point(s) per workstream, open/escalate items, recent decisions), at project or whole-fleet altitude. |
status | human | the one-line-per-workstream fleet cockpit: handle · liveness · heartbeat recency · the Needs-you band (open escalate items). |
brief and render share the same byte-identical fold of the log: the human reads the same picture a fresh session reads. A fourth, narrower projection, open-items, lists a workstream's unresolved open-items (ULID + body); it exists to feed resolve and /director:complete. It defaults to the current workstream; --workstream <id> retargets it at a sibling: the close-out path for a workstream whose session is already gone.
The digest is deliberately an index: every line is capped to a headline so the injection stays small as a project's log grows, and nothing is lost: show <ulid> prints any single event in full (body verbatim, as recorded, plus one derived lifecycle: line when the fold has retired it), one deterministic hop from any headline. When even the capped digest would overrun the injection budget, it degrades in a fixed order and the overflow lands in health/ as a grooming signal: older decisions collapse to a count-plus-pointer line (up to the 10 newest decisions this workstream has not seen stay); if that is not enough, open-item headlines shorten to 160 characters (the full text stays one show away); only then do the unseen decisions collapse too. No open-item or resume point is ever dropped. The grooming verbs that keep that headroom are resolve (compacts the open-set), supersession via --refs, and promote (compacts the decision set into the docs).
Machine consumers can use render --json instead of parsing the presentation-oriented digest. The JSON envelope has its own schema_version, independent of the durable event schema. It contains the active Decisions, open Open Items, and resumable Handoff stacks in deterministic order; every record includes its lifecycle and complete event. show --json <ulid> returns any current or historical event with its folded lifecycle and, for a retired one, the id of the event that retired it. The default text output remains unchanged.
register / heartbeat / done maintain a workstream's liveness row and are normally fired by the hooks, not run by hand. Liveness is derived from heartbeat age (TTL/lease), never self-declared: a workstream that stops heartbeating ages to idle (after 4h) then dormant (after 2d), and dormant is a first-class state (a project parked between blocks), not a fault. A workstream whose branch no longer exists reads gone regardless of heartbeat: it looks complete and is the /director:complete candidate. Because git refuses to delete a checked-out branch, a gone workstream is always a sibling (a worktree that merged and was removed), never the session's own, so the surfacing happens one session later: status shows the gone row's open-item count with the remedy, and, if the gone workstream still owns open items, the next agent session on that repo gets a session-start nudge naming it (a zero-loop corpse has nothing at risk, so it gets the status remedy only). /director:complete <workstream-id> then closes out the dead sibling from wherever you are in the repo (done --workstream <id> archives every row it left behind). In practice gone rarely fires today: a session's Stop hook archives its fleet row every turn and the next heartbeat recreates it bare, so rows usually lose their branch and dir info after a session's first turn (#30), and a row without them fails open and never reads gone. As a result, gone and the session-start nudge built on it rarely appear; closing out a dead sibling is a manual /director:complete <workstream-id>, best run right after its PR merges. done archives rows to fleet/archive/<date>/ rather than deleting them.
There are exactly four model-emitted semantic kinds. Pick by what the fact is:
| Kind | Use it for | Lifecycle |
|---|---|---|
decision | a choice + what it affects | active → superseded (another decision's --refs, any workstream, no ordering check) or promoted (via promote); carries --risk low|escalate |
open-item | an open loop / follow-up / deferred item, the canonical home for "documented, not dropped" | open → closed (via resolve) |
handoff | a positional snapshot: current task · next action · hypotheses · dead ends (tried X, failed: Y) | active → superseded (a later same-workstream handoff's --refs: exactly the positions it names, nothing older, nothing newer; a handoff carrying no such refs retires all older ones) or concluded (a note's --conclude via /director:complete); either way it leaves the digest, stays in the log |
note | FYI / context for a parallel or future session; a finished task's outcome (a review verdict, an investigation result) | none |
blocker kind. "Stuck, needs a human" is an open-item with --risk escalate, exactly the open-set that surfaces in status's Needs-you band.done is not a semantic kind: it is fleet-liveness only (a hook marks the session terminal). "What's done" belongs in a handoff body when the work resumes, or in the task-outcome note when it doesn't.The SessionStart hook injects this protocol into every managed-repo session, so the emit habit is in context from turn one: pushed as injected state, not shipped as a lazy model-invoked skill, because an always-on habit only fires if it is already in the window. (skills/director/SKILL.md is the fuller, readable version of the same guidance.) It teaches a session two load-bearing habits that no hook can perform for it:
handoff at each natural boundary), never batched for the end of a session. /director:handoff is the one boundary command: in a worktree whose PR is already merged and whose tree is clean it runs the /director:complete close-out instead of writing a resume point (running /director:complete directly stays valid), and a finished one-shot task such as a PR review takes a note, never a handoff. Transient working state survives a compaction only if the model wrote it to the LOG during a turn. An emit rides along with the session's next tool call, its body on stdin as a quoted heredoc, so the habit usually costs a tool call, not a turn.A workstream's id is <repo>-<branch>-<shortid>, derived deterministically from a canonical repo-key (a git fallback chain that collapses worktrees, prefers a normalized remote, and falls back to the common-dir path) plus the branch, and persisted at .director/workstream-id in the worktree. A resumed or compacted session re-derives the same id and updates the same fleet row; after a branch rename the persisted id stays put. This stability is what makes the fleet free of zombie rows.
In v1: the hook-first coordination core (CLI write path, identity, event store, fleet/liveness, render/brief/status, hooks + the _managedBy installer, the injected coordination protocol), informed adoption (adopt registers; /director:adopt drafts the CHARTER proposal and runs the triaged open-loop import; see Adopt an existing repo), and a Codex adapter: director install --codex wires the same hooks into Codex's hooks.json (Codex asks you to trust them at the next session start; if you dismiss that prompt, run /hooks in the session) and installs the boundary commands as agent skills: $director-adopt, $director-complete, $director-handoff. Ground truth injection, liveness, and close-out work identically on both of those agents; the emit-guard and the context-fill handoff nudge are Claude Code-only for now (they read CC's transcript format and stay safely inert on Codex). Single-machine. Since v1 (v1.9.0): an OpenCode adapter, director install --opencode — a managed plugin plus the boundary commands as /director-* custom commands; injection, liveness, and close-out work as on Claude Code, and the CC-only nudges stay safely inert there too. Newest (v1.12.0), a GitHub Copilot CLI adapter, director install --copilot: one Director-owned hooks file at ~/.copilot/hooks/director.json (no trust step, no config file of yours touched) driving the same shims, with the boundary commands shared with the Codex agent skills; injection, liveness, and close-out work as on Claude Code, session-end row reaping included, and the CC-only nudges stay inert there as well. Also since v1, handoff supersession is explicit: a checkpoint retires only the resume point(s) its --refs name, so two parallel sessions on one workstream now stack their positions in the digest instead of the later one silently overwriting the earlier, and the next session consolidates the stack into a single handoff that refs each of them.
Deferred: deeper brownfield analysis beyond the informed-adopt pass (doc living/record/rot reconciliation, an arc42 overview draft, back-dated decision records). brief --synthesize (model-narrated prose) is deferred: v1 ships the deterministic brief. A background monitor/reaper, notifications, and a freshness sweep come later.
Multi-machine is the one distribution mode on the roadmap, and its shape is settled: the hub becomes git-synced, and the merge is just the fold (the fold is a pure, order-independent function of the event set, so per-machine logs merge as set union). No server, no protocol, no conflict resolution, ever.
Multi-user is different: not deferred, out of scope by design. Director externalizes one human's in-flight working memory; sessions are plural, machines are plural, humans are not. A team syncs through the slower artifacts (plans, architecture docs), never through a shared in-flight log.
Quality gate (the bar for "done"):
| Property | Guarantee |
|---|---|
| No data loss | zero lost entries under N concurrent emit writers and across resume-after-compaction (local filesystems; an NFS-mounted hub is a known limitation, with multi-machine deferred) |
| Render determinism | same inputs → byte-identical render and brief; render --verify passes |
| Identity stability | one workstream keeps one id across resume/compaction |
| Fail-safe hooks | a broken hook never blocks session start (failure surfaces in health/) |
DIRECTOR_HUB (default ~/.director) holds all cross-repo coordination state:
$DIRECTOR_HUB/
├── projects/<repo-key>/
│ ├── CHARTER.md # living source of record: goal, non-goals, risk-line
│ └── log.ndjson # append-only typed events (decision · open-item · handoff · note)
├── fleet/
│ ├── <workstream>--<uuid>-<hash>.json # liveness row per session; -<hash> avoids slug collisions
│ └── archive/<date>/ # terminal 'done' rows — archived, never deleted
└── health/ # hook health log + render manifests
In each adopted worktree: .director/workstream-id (and .director/repo-key), tiny and stable. To keep them out of git, Director writes .director/.gitignore (a comment and *, which also ignores the file itself), best effort: whenever identity resolves and finds that file missing or empty (an interrupted write), it writes it. If .director/ cannot be written, or is a symlink, nothing is ignored and identity still works. Each linked worktree gets its own, a non-empty existing .director/.gitignore is never rewritten, and your repo's own .gitignore and .git are never touched. Without the file git add -A would commit the id and every other clone or worktree would inherit it. Ignore rules do not apply to files git already tracks: a repo that committed .director/ before this fix needs git rm -r --cached .director and a commit.
# 1. Build the binary, put it on PATH, wire in your agent(s)
go build -o bin/director ./cmd/director
sudo install bin/director /usr/local/bin/director
director install # Claude Code (the default)
director install --codex # OpenAI Codex (--opencode wires OpenCode)
director install --copilot # GitHub Copilot CLI; targets combine (--codex --copilot)
director install --all # or wire all four agents in one go
director doctor # confirm the wiring will actually fire
# 2. Register an existing repo in the fleet
cd ~/dev/src/some-project
director adopt
# 3. Open an agent session in that repo and run /director:adopt (Codex and
# Copilot CLI: $director-adopt; OpenCode: /director-adopt) — it drafts the
# CHARTER from the repo's docs and triages
# its real open loops for import, everything confirmed by you (or skip it
# and fill in the CHARTER stub by hand). From here on, every session start
# injects CHARTER + digest as Ground Truth.
# 4. See the cockpit
director status
# some-project-main-1a2b3c4d · active · just now · ok
director doctorDIRECTOR_HUB | ~/.director | the central hub that holds all cross-repo coordination state |
DIRECTOR_BIN | (PATH) | which director binary the shims invoke (defaults to director on PATH, then the symlink install drops next to the shims at <hooks dir>/../bin/director, ~/.claude/director/bin/director by default) |
DIRECTOR_DISABLE | (unset) | 1 or true turns every hook into a no-op: no digest injection, no emit guard, no fleet rows, nothing written to the hub. Use it for throwaway clones of an adopted repo (review sandboxes, CI checkouts, dispatched agents): identity is keyed by the origin URL, so they are otherwise treated as the project. Hooks only: the CLI keeps working. director doctor warns when it is set, including when it is pinned in ~/.claude/settings.json "env", which disables Director in every Claude Code session |
DIRECTOR_HANDOFF_NUDGE_TOKENS | (unset) | the context-fill handoff nudge (Claude Code-only for now): an absolute token threshold at which sessions are nudged toward /director:handoff; unset or 0 disables it. Fires once per crossing and re-arms only after context falls below half the threshold (a compaction or a context clear) |