OpenFleet reference implementation: @logicsrc/openfleet, logicsrc fleet, and Claude Code hooks - #185
Merged
Merged
Conversation
…icsrc fleet 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
…s, rule 6 in tree, lost only for what a roster can hold The review of the reference implementation against moshcode found the two readers disagreeing on the same files. This round applies the shared rulings so both sides read a ledger the same way. Ceiling (R-A, R-B, R-C, R1, R6, R10, R15, R17): memberCeiling rebuilds the effective ceiling from the ledger on every read. The latest fleet-target fleet.cap (else fleet.open, else the implicit fleet's) replaces the copy in a record, so a sysop's widening cap reaches running members; then each swarm.spawn narrowing down the path, then swarm caps last. In the implicit fleet a parentless record's own approvals enters at the root; a ceiling a writer left without the key is never read as native, and startMember fills it with the engine's word while the record is unclaimed. A fleet.open or cap with no hosts means the host it was written on (R23). Once-markers (R-G, R28): member.start, member.end and swarm.end each take an exclusive create under fleets/<fleet>/marks/<event>.<id> before the append; a lost end takes <id>.lost so a real end can still supersede it. The hooks let a real end follow a lost line (R9). tree (R-F, R20): run by the sysop it enforces rule 6, stopping a member past its effective until with state timeout and the members of a swarm or fleet at its budget with state budget, then writes swarm.end for each swarm touched once it is complete. An agent's tree stops nothing. lost is written only for a member its engine's roster can hold: a claude-code background job (8-hex member or session) or a moshcode pane, never an interactive session claude agents does not list (R-E, R3, R14). A nested swarm is drawn under the member that spawned it and its row shows the effective ceiling (R25). stop and cap (R-D, R-H, R22, R27): swarm.end is written only once every member and every nested swarm has an end line that counts; an engine that will not end a member leaves it without an end line and the verb exits non-zero. claude stop takes the job id: the member of a background job, else the first eight characters of a session UUID; an interactive session with no job id cannot be stopped and the tool says so. cap on a swarm refuses a key that would widen. A derived claude-code job is named by its job id and carries no pid. Also: R-I (endMember ends only the engine-minted swarm of one), R35 (a derived record's guessed approvals corrected at UserPromptSubmit), R32 (the UserPromptSubmit hook passes only exit 2 through), R31 (package README), R36 (rule 13 says the launcher test is unimplemented in 0.1), docs and PRD 0008 updated for lost, rule 6 and the markers. 113 openfleet tests, 93 CLI tests, contract green. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RZV4zJ2pDZLNN3kE5jFCmV
ThreatCrush Security Scan22 finding(s) HIGH/CRITICAL: 3 | MEDIUM: 10 | LOW: 9
Snippets are redacted; ThreatCrush never prints matched credential material. |
A first prompt refused by the ceiling still lets the session wind down through Stop and SessionEnd; those handlers now write nothing when the ledger holds no member.start for the member, so a refused member is never drawn as done. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RZV4zJ2pDZLNN3kE5jFCmV
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RZV4zJ2pDZLNN3kE5jFCmV
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
OpenFleet reference implementation: @logicsrc/openfleet,
logicsrc fleet, and Claude Code hooksImplements the OpenFleet specification shipped in #179. The spec described what Claude Code, moshcode and a
logicsrc fleettool would each add; this PR builds the logicsrc side, and the Claude Code side through hooks. The moshcode side lands in moshcoder/moshcode 0.99.0.@logicsrc/openfleet0.1.0 (new workspace package): the record and ledger library. Home under~/.openfleet(OPENFLEET_HOME), records atfleets/<fleet>/members/<member>.json, one append-onlyledger.jsonlper fleet per host merged byat, the eight events, the ceiling merge (fleet base, swarm narrowings root to leaf, swarm caps last, never widening), the claim rule (an unclaimed record is claimed by the first session that writes itsmember.start; a claimed one makes a child derive), root and orphan records, and a fold that renders the landing page's tree from the files alone, with engine rosters joined for liveness.logicsrc fleet(CLI 0.2.1 to 0.3.0):openandcapfor the sysop alone (refused with exit 4 from any process carryingOPENFLEET_MEMBER),tree,stop(through each member's own engine:claude stop,moshcode herd kill, tmux, or a pid; nested swarms first; oneswarm.end) andlog(--since,--member,--swarm,--json).treealso enforces deadlines and budgets.Claude Code through hooks (
logicsrc fleet hooks install|remove|status): merges five hook entries into~/.claude/settings.jsonwithout touching anyone else's (matched by command text, removed only ours, write then rename). SessionStart claims or derives the record named byOPENFLEET_RECORD, else writes a root record, exports the five variables to the session's tools, and tells the model its member id and piece. The first prompt writesmember.startwith approvals taken from the live permission mode, or refuses withceiling.refuseand exit 2. PreToolUse denies Edit and Write outside the member's owned paths. Stop and SessionEnd writemember.endwith the summary. Verified with realclaude -psessions: a hand-written unclaimed record was claimed, all five variables reached the Bash tool, a write outside the owned paths was denied while the owned one went through, andmember.endcarried the final message.Two limits found while probing, now stated in the spec: a background job dispatched from
claude agentsreceives no launcher environment, so it becomes a root member unless a launcher writes its record; and env-file exports reach the member's tools but not later hooks, so hooks key on the session id throughsessions/<session_id>.json.Also: docs and the landing page now say what ships instead of "none ships yet"; PRD 0008; README and docs/cli.md mention the group.
Verification.
@logicsrc/openfleetvitest suite,@logicsrc/clisuite,tsc --noEmiton the CLI,npm run test:contract,next build, an end-to-end run of every verb against a temp home, two liveclaude -phook gates, and a cross-tool interop run in which moshcode 0.99.0 wrote a swarm andlogicsrc fleet treerendered it. Three adversarial review rounds; every blocker and major fixed.🤖 Generated with Claude Code
https://claude.ai/code/session_01RZV4zJ2pDZLNN3kE5jFCmV