Ship what docs/openfleet.md describes. The new workspace package holds the record (write once, never overwrite, 0600), the ledger (append-only JSON Lines, merged across ledger*.jsonl by at), the ceiling rules (whole fleet ceiling, narrowed swarm keys, a merge that never widens, refusals by key), claiming and deriving exactly as the spec's "Claiming and deriving" and rule 13, and fold(), which turns any $OPENFLEET_HOME plus the engine rosters into the tree the landing page shows. logicsrc fleet open|cap|tree|stop|log are the sysop's verbs, every one with --json. open and cap exit 4 when OPENFLEET_MEMBER is set; stop exits 4 outside the caller's subtree, ends nested swarms first, goes through each member's own engine (claude stop, moshcode herd kill, tmux kill-pane, a signal for claude-p) and writes one swarm.end per swarm. tree reads claude agents --json --all and ~/.moshcode/herd/sessions.json when it can, draws recordless sessions as roster roots of the implicit fleet, and writes member.end lost for a recorded member its engine no longer lists. Claude Code takes part through hooks: logicsrc fleet hooks install merges SessionStart, UserPromptSubmit, PreToolUse, Stop and SessionEnd into ~/.claude/settings.json without clobbering it, and logicsrc fleet hook <Event> runs each one. SessionStart claims, derives or writes a root record and hands the member its variables through CLAUDE_ENV_FILE; UserPromptSubmit checks the ceiling with the permission mode the engine reports and writes member.start, or refuses the first prompt with exit 2 and ceiling.refuse; PreToolUse denies an edit outside piece.owns; Stop and SessionEnd write member.end. A hand-started root takes the engine's reported approvals before member.start, since the command line only guesses them. Hooks never fail the engine: everything is caught and logged to hooks.log. The spec and the landing page now say what ships, keep Status 0.1, and record the two verified Claude Code limits: a background job dispatched from claude agents gets no launcher environment, and OPENFLEET_* exported at SessionStart reach the member's tools but not later hooks, so hooks key on session_id through $OPENFLEET_HOME/sessions/<session_id>.json. PRD 0008 covers the work. CLI 0.2.1 -> 0.3.0; build and build:cli chains build the package before the CLI; README and docs/cli.md list the group. Tests: 95 in the package (record, ledger merge, every narrower case, the worked example's claim and derive, the folded tree, hook install idempotence, each hook handler including the exit-2 refusal and the PreToolUse deny, every verb with fake deps) and 4 in the CLI. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RZV4zJ2pDZLNN3kE5jFCmV
5.8 KiB
| openprd | id | title | status | authors | created | updated | repo | discussion | implementation | tags | supersedes | superseded-by | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0.3 | 0008 | Ship the OpenFleet reference implementation | Draft |
|
2026-09-13 | 2026-09-13 | profullstack/logicsrc | packages/openfleet |
|
Problem
The OpenFleet specification (docs/openfleet.md) was published on 2026-09-13 from
one incident: a Claude Code background job asked moshcode to split a task across
two agents, and afterwards nobody, human or agent, could say who had started
either worker, why, under what ceiling, or with whose approval. The spec names
the record, the ledger and five sysop verbs, and named logicsrc fleet as the
reference sysop tool. It shipped with the line "None of the three ships yet".
A standard nothing implements is prose; the incident repeats every morning until
the files exist and something writes them.
Goals
- A human can open a fleet, set its ceiling, see every agent session under them
as one tree, stop a swarm as one unit, and read afterwards what happened and
who did it, from one command:
logicsrc fleet. - Every Claude Code session on a box with the hooks installed becomes a recorded member: it claims the record its starter wrote, or derives its own, or is a root member of the implicit fleet, and it refuses to run above the ceiling it was started under.
- moshcode and Claude Code write the same files the same way, so one tree shows both engines' members without either reading the other's roster.
Non-Goals
- No orchestration: splitting a task, choosing an engine, verifying, and
synthesising stay in
moshcode swarmand@logicsrc/agentswarm. - No change to Claude Code itself. The engine side ships as hooks over its own
settings file; FleetView grouping and
member.spendat intervals wait on the engine. - No network surface, no signed ledger lines, no freeze, no
adopt: the 0.2 questions stay open.
Users
- The sysop: one developer answerable for every agent session on their box, who wants to see the tree and stop the wrong part of it.
- An agent that spawns a swarm and wants its children to know who they are and what they own.
- A later session inspecting a member's record to answer the five questions the incident could not.
Requirements
- R1 [P0]
@logicsrc/openfleet0.1.0: the record (write once, never overwrite), the ledger (append-only, 0600, merged acrossledger*.jsonlbyat), the ceiling rules (whole fleet ceiling, narrowed swarm keys, merge that never widens, refusal by key), claim and derive exactly as the spec's "Claiming and deriving", andfoldinto the tree the landing page shows. - R2 [P0]
logicsrc fleet open|cap|tree|stop|logwith the spec's flags;openandcapexit 4 whenOPENFLEET_MEMBERis set;stopexits 4 outside the caller's subtree;stopends nested swarms first and writes oneswarm.endper swarm; every verb takes--json. - R3 [P0]
stopgoes through the member's own engine:claude stopforclaude-code,moshcode herd killformoshcode/*,tmux kill-panefortmux, a signal forclaude-p. Never a shell string. - R4 [P0] Claude Code hooks:
logicsrc fleet hook <Event>for SessionStart, UserPromptSubmit, PreToolUse, Stop and SessionEnd, andlogicsrc fleet hooks install|remove|statusthat merges into~/.claude/settings.jsonand never clobbers it. A hook never fails the engine; a refused start exits 2 before anymember.start. - R5 [P1]
treereadsclaude agents --json --alland~/.moshcode/herd/sessions.jsonwhen it can, draws recordless sessions as roots of the implicit fleet, and writesmember.endstatelostfor a recorded member its engine no longer lists. - R6 [P1] The spec and the landing page say what ships, keep
Status: 0.1, and record the two verified Claude Code limits (no launcher environment reaches a dispatched background job; exported variables reach tools but not later hooks). - R7 [P1] Tests cover record and ledger IO, every narrower case, the worked example's claim and derive, the folded tree, hook install idempotence, and each hook handler, including the exit-2 refusal and the PreToolUse deny.
UX Notes
logicsrc fleet tree prints the tree the landing page shows: fleet header,
root members, swarms nested under their spawner, members with engine, state,
[bypass], owns. log prints one line per event, oldest first, with who did
it. Refusals name the key, what was wanted and what was allowed.
Tech Stack
TypeScript, NodeNext, commander 14, vitest 4. No workspace dependencies beyond
the CLI's file:../openfleet link. Node 18+ (the installer's floor), so the
tree is plain text rather than a TUI.
Monetization
None. It is the reference implementation of an open standard.
Success Metrics
- The worked example's morning can be replayed against a temp home and
logicsrc fleet treeprints the tree the spec shows. - A Claude Code session started with the hooks installed appears in
logicsrc fleet treewith the right approvals mark without anyone editing a file by hand.
Risks & Open Questions
- A background job dispatched from
claude agentsgets no launcher environment, so a launcher that wants it in a swarm must write its record and pass the path another way (a--settingshook command, or a lookup by the job's cwd and intent). Until then it is a root of the implicit fleet. - User-level hooks fire for every
claude -pa tool makes, so each becomes a swarm of one and, at depth 1 in the implicit fleet, is refused on depth. The spec lists this as an open question; the hooks enforce the letter of it. hasEventbefore a write is a check, not a lock. Two writers racing on one swarm can still produce twoswarm.endlines.