mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-10-02 04:43:58 +00:00
OpenFleet reference implementation: @logicsrc/openfleet 0.1.0 and logicsrc 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
This commit is contained in:
parent
123ba2e39a
commit
4ceaaaaa1f
34 changed files with 5510 additions and 15 deletions
|
|
@ -35,6 +35,7 @@ accounts
|
|||
social
|
||||
email
|
||||
credentials
|
||||
fleet
|
||||
openspec
|
||||
plugins
|
||||
tui
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ Five verbs, over `$OPENFLEET_HOME`. Two are the sysop's alone. The test is the e
|
|||
| `stop` | anyone, within reach | Ends a member, a swarm, or everything in a fleet (`--fleet`) as one unit. For a swarm: nested swarms first, each with its own `swarm.end`, then the target's members through each member's own engine, then the target's `swarm.end`. An agent may stop only a swarm it spawned or a member under such a swarm; `--fleet`, an ancestor, or a sibling's swarm refuses when `OPENFLEET_MEMBER` is set. |
|
||||
| `log` | anyone | Reads the ledger for a fleet, a swarm or a member: what happened, in order, who did it, what each member spent, how each ended, what was refused and why. `--since`, `--member`, `--swarm`, `--json`. |
|
||||
|
||||
The reference sysop tool is `logicsrc fleet`. FleetView (`claude agents`) and `moshcode fleet` would offer the same five verbs over the same files, each able to stop the members its own engine runs. None of the three ships yet.
|
||||
The reference sysop tool is `logicsrc fleet`, in `@logicsrc/openfleet` 0.1.0 (logicsrc CLI 0.3.0). `moshcode fleet` offers the same five verbs over the same files from moshcode 0.99.0, and stops the members its engine runs. FleetView (`claude agents`) does not offer them yet; Claude Code takes part through the hooks `logicsrc fleet hooks install` writes, and `logicsrc fleet` stops its jobs through `claude stop`.
|
||||
|
||||
## Rules
|
||||
|
||||
|
|
@ -200,13 +200,13 @@ Had `460a4502` been started with approvals `native`, its subtree's ceiling would
|
|||
|
||||
## Reference implementations
|
||||
|
||||
None ships yet. What each tool adds, from what it records today:
|
||||
Three ship: `logicsrc fleet` in `@logicsrc/openfleet` 0.1.0 (logicsrc CLI 0.3.0), Claude Code through `logicsrc fleet hooks install`, and moshcode 0.99.0, which writes records in `moshcode swarm` and offers `moshcode fleet`. What each does, on top of what it records:
|
||||
|
||||
**Claude Code.** Resolve the fleet from `OPENFLEET_FLEET`, else `$OPENFLEET_HOME/current`, else the implicit `<user>@<host>`. On every start, interactive, `--bg`, `-p`, or dispatched from `claude agents`: when `OPENFLEET_RECORD` names an unclaimed record, claim it; when it names a claimed one, derive a child record and claim that; when it is unset, write a root record, with `orphan: true` when the environment the `claude` command was invoked with carries a child marker; for a `--bg` job the launching process makes that test, not the daemon. A root record written with no opened fleet carries `ceiling: { approvals, depth: 1, hosts: [host] }` from its own flags, or `approvals: native` when the record carries `orphan`. Set `approvals` to `bypass` whenever the flags carry `--permission-mode bypassPermissions` or `--dangerously-skip-permissions`; refuse the start with `ceiling.refuse` when the ceiling says no, before any `member.start`; set the four variables below `OPENFLEET_HOME` in the job environment beside `CLAUDE_JOB_DIR`. Add the record's `fleet`, `swarm`, `parent`, `depth` and `approvals` to `~/.claude/jobs/<id>/state.json` and to the `claude agents --json` rows, so the roster stops being flat. Append `member.spend` from the job's token count at intervals, and `member.end` when the job first goes terminal, or when an interactive or `-p` session exits, or on SIGHUP when it can, with `summary` from the job's final output, or its SUMMARY section when the prompt asked for one, and `links` from the state file's `children` key, which holds the links a job produced and not child jobs. FleetView groups rows by fleet and swarm, marks `bypass` members, sums spend against budget, and offers `stop` on a swarm and `cap`. The in-process Agent and Workflow tools stay as they are: not members. One thing is unverified: whether the `claude agents` view passes its environment to the daemon it starts. If it does not, the view passes the four variables itself, and the fallback is a root member of the implicit fleet.
|
||||
**Claude Code.** Ships as hooks. `logicsrc fleet hooks install` merges five entries into `~/.claude/settings.json` and never clobbers what is there: SessionStart resolves the record and hands the member its variables, UserPromptSubmit checks the ceiling with the permission mode the engine reports and writes `member.start` or refuses the first prompt, PreToolUse denies an Edit or Write outside `piece.owns`, Stop and SessionEnd write `member.end`. What the hooks do is what this paragraph asks of the engine. Resolve the fleet from `OPENFLEET_FLEET`, else `$OPENFLEET_HOME/current`, else the implicit `<user>@<host>`. On every start, interactive, `--bg`, `-p`, or dispatched from `claude agents`: when `OPENFLEET_RECORD` names an unclaimed record, claim it; when it names a claimed one, derive a child record and claim that; when it is unset, write a root record, with `orphan: true` when the environment the `claude` command was invoked with carries a child marker; for a `--bg` job the launching process makes that test, not the daemon. A root record written with no opened fleet carries `ceiling: { approvals, depth: 1, hosts: [host] }` from its own flags, or `approvals: native` when the record carries `orphan`. Set `approvals` to `bypass` whenever the flags carry `--permission-mode bypassPermissions` or `--dangerously-skip-permissions`; refuse the start with `ceiling.refuse` when the ceiling says no, before any `member.start`; set the four variables below `OPENFLEET_HOME` in the job environment beside `CLAUDE_JOB_DIR`. Still Claude Code's own to add: the record's `fleet`, `swarm`, `parent`, `depth` and `approvals` in `~/.claude/jobs/<id>/state.json` and in the `claude agents --json` rows, so the roster stops being flat; `member.spend` from the job's token count at intervals; and a FleetView that groups rows by fleet and swarm, marks `bypass` members, sums spend against budget, and offers `stop` on a swarm and `cap`. Until then the hooks write `member.end` when a background job first goes terminal, or when an interactive or `-p` session exits, with `summary` from the closing message, or its SUMMARY section when the prompt asked for one, `total` from the job's token count, and `links` from the state file's `children` key, which holds the links a job produced and not child jobs. The in-process Agent and Workflow tools stay as they are: not members. Two limits were verified on Claude Code 2.1.270 and shape the hooks. A background job dispatched from `claude agents` runs in a spare the daemon forked before any launcher existed, so no launcher environment reaches it: it is a root member of the implicit fleet unless a launcher writes its record and the job finds that record by another channel than the environment. And `OPENFLEET_*` exported at SessionStart reach the member's tools through `CLAUDE_ENV_FILE` but not later hooks, so the hooks key on `session_id` through `$OPENFLEET_HOME/sessions/<session_id>.json`, a file of the implementation and not of this spec.
|
||||
|
||||
**moshcode.** In `moshcode swarm`: mint the swarm id before the plan call, and run the planner with no `OPENFLEET_SWARM`, since its `swarm.spawn` does not exist until the plan returns; extend the planner's reply to `[{ "title", "prompt", "files" }]` and store `files` as `piece.owns`, so "do not touch bye.sh" becomes data moshcode can check instead of prose it never parses; write `swarm.spawn` with one piece per pane, member ids `<swarm>-<n>`, and the narrowing from `--agents` (`fan_out`) and `--timeout` (`until`); name each pane after its member id and write one unclaimed record per pane with `session` the pane's tmux target; add the four variables to the pane's environment line beside `MOSHCODE_HERD_NAME` and `MOSHCODE_HERD_DIR`, and keep them when deleting `ANTHROPIC_API_KEY` and `CLAUDE_CODE_SESSION_ID`. A `claude` pane claims its own record. For codex, deepseek and kimi panes moshcode writes `member.start` from the herd ledger's `submit` event, since nothing else in the pane writes a record. Record `approvals: bypass` truthfully: today `sessions.json` says `agent: false` while the pane runs `claude --dangerously-skip-permissions`. Refuse that flag with `ceiling.refuse` unless the ceiling says `bypass`. At the end, write `member.end` from the herd `end` event for every pane whose session has not written its own by then, then `swarm.end` with the synthesis as `summary` and the `--verify` result as `verdict`, then the default kill; `--keep` leaves members running and writes neither `member.end` nor `swarm.end`. When moshcode itself runs inside a member, the swarm's parent is that member. `moshcode fleet open|cap|tree|stop|log` is the sysop tool for this engine, with `herd ps` grouped by fleet and swarm.
|
||||
**moshcode.** From 0.99.0, `moshcode swarm` writes the record and the ledger, and `moshcode fleet` is the sysop tool for its engine. What that means, in `moshcode swarm`: mint the swarm id before the plan call, and run the planner with no `OPENFLEET_SWARM`, since its `swarm.spawn` does not exist until the plan returns; extend the planner's reply to `[{ "title", "prompt", "files" }]` and store `files` as `piece.owns`, so "do not touch bye.sh" becomes data moshcode can check instead of prose it never parses; write `swarm.spawn` with one piece per pane, member ids `<swarm>-<n>`, and the narrowing from `--agents` (`fan_out`) and `--timeout` (`until`); name each pane after its member id and write one unclaimed record per pane with `session` the pane's tmux target; add the four variables to the pane's environment line beside `MOSHCODE_HERD_NAME` and `MOSHCODE_HERD_DIR`, and keep them when deleting `ANTHROPIC_API_KEY` and `CLAUDE_CODE_SESSION_ID`. A `claude` pane claims its own record. For codex, deepseek and kimi panes moshcode writes `member.start` from the herd ledger's `submit` event, since nothing else in the pane writes a record. Record `approvals: bypass` truthfully: today `sessions.json` says `agent: false` while the pane runs `claude --dangerously-skip-permissions`. Refuse that flag with `ceiling.refuse` unless the ceiling says `bypass`. At the end, write `member.end` from the herd `end` event for every pane whose session has not written its own by then, then `swarm.end` with the synthesis as `summary` and the `--verify` result as `verdict`, then the default kill; `--keep` leaves members running and writes neither `member.end` nor `swarm.end`. When moshcode itself runs inside a member, the swarm's parent is that member. `moshcode fleet open|cap|tree|stop|log` is the sysop tool for this engine, with `herd ps` grouped by fleet and swarm.
|
||||
|
||||
**logicsrc.** `logicsrc fleet open|cap|tree|stop|log`, the engine-neutral sysop tool that folds any `$OPENFLEET_HOME` into one tree and stops a member through the engine its record names.
|
||||
**logicsrc.** `logicsrc fleet open|cap|tree|stop|log`, the engine-neutral sysop tool that folds any `$OPENFLEET_HOME` into one tree and stops a member through the engine its record names: `claude stop` for `claude-code`, `moshcode herd kill` for `moshcode/*`, `tmux kill-pane` for `tmux`, a signal to the pid for `claude-p`. Ships in `@logicsrc/openfleet` 0.1.0 with the logicsrc CLI 0.3.0. `tree` reads `claude agents --json --all` and `~/.moshcode/herd/sessions.json` for liveness and for members with no record, and writes `member.end` state `lost` for a recorded member its engine no longer lists. `logicsrc fleet hooks install|remove|status` and `logicsrc fleet hook <Event>` are the Claude Code side above. Every verb takes `--json`.
|
||||
|
||||
## What is deliberately absent
|
||||
|
||||
|
|
@ -258,7 +258,7 @@ None ships yet. What each tool adds, from what it records today:
|
|||
|
||||
| Version | Date | Change |
|
||||
|---|---|---|
|
||||
| 0.1 | 2026-09-13 | First publication: fleet and swarm, the record, the environment, claiming and deriving, the ledger's eight events, five sysop verbs, fifteen rules, the worked example, what Claude Code and moshcode would each add. |
|
||||
| 0.1 | 2026-09-13 | First publication: fleet and swarm, the record, the environment, claiming and deriving, the ledger's eight events, five sysop verbs, fifteen rules, the worked example, what Claude Code and moshcode each add. Same day: `logicsrc fleet` and the Claude Code hooks ship in `@logicsrc/openfleet` 0.1.0, `moshcode swarm` and `moshcode fleet` in moshcode 0.99.0. |
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue