From 067c21cb8c4cdd5794352ce646a2533eb5c86386 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Fri, 25 Sep 2026 07:10:39 +0000 Subject: [PATCH] feat(tools): install swamp, and PRD 0019 for what to take from it Swamp (swamp-club.com, github.com/systeminit/swamp) is a deterministic automation CLI: typed models, workflow DAGs, versioned immutable runs, and a vault that injects credentials at execution time so they never reach a prompt. Wired as a TOOL rather than an engine. `/agents` is an alias for `engines` (cli-schema.mjs:1751) and engines are interactive coding agents you land a herd pane on. Swamp is driven BY an agent, ships skills into .claude/skills, .cursor/skills and .agents/skills for Claude Code, Cursor, OpenCode and Codex, and has no interactive session, so it belongs with railway, gh and supabase. Moving it is a one-line change if that call is wrong. Two installer details the entry has to carry: the vendor script prompts for SWAMP CLUB signup partway through, which would stall a piped install, so SWAMP_NONINTERACTIVE is set; and a non-root install lands in ~/.local/bin, ~/bin or ~/.swamp/bin depending on what is already on PATH, while the script edits no shell rc, so all three are searched. PRD 0019 covers what is worth taking. The lead item is heartbeat liveness: moshcode infers engine state by regex-matching vendor output, which is why Claude Code 2.1 broke detection when it overwrote the pane title and dropped "? for shortcuts". Swamp tracks runs by heartbeat instead. Then immutable run history, swarm pieces as a DAG rather than a fixed phase list, `moshcode doctor`, and documented config precedence, which PR #535 paid for the lack of. The binding constraint is in Non-Goals: swamp is AGPL-3.0 and moshcode is MIT, so the ideas are rippable and the code is not. Co-Authored-By: Claude Opus 5 (1M context) --- prd/0019-take-what-swamp-got-right.md | 173 ++++++++++++++++++++++++++ prd/README.md | 1 + src/tools.mjs | 38 ++++++ 3 files changed, 212 insertions(+) create mode 100644 prd/0019-take-what-swamp-got-right.md diff --git a/prd/0019-take-what-swamp-got-right.md b/prd/0019-take-what-swamp-got-right.md new file mode 100644 index 00000000..bdaf0cdf --- /dev/null +++ b/prd/0019-take-what-swamp-got-right.md @@ -0,0 +1,173 @@ +--- +openprd: "0.3" +id: "0019" +title: "Take what swamp got right: heartbeat liveness, immutable run history, and workflow DAGs" +status: Draft +authors: + - anthony@profullstack.com +created: 2026-09-25 +updated: 2026-09-25 +repo: https://github.com/moshcoder/moshcode +discussion: +implementation: src/tools.mjs, src/herd-state.mjs, src/openfleet.mjs, src/swarm.mjs, src/moshscript.mjs, src/skills.mjs, src/settings-sync.mjs +tags: + - herd + - openfleet + - swarm + - moshscript + - dx +supersedes: +superseded-by: +--- + +## Problem + +Swamp (swamp-club.com, github.com/systeminit/swamp, by System Initiative) is a +deterministic automation CLI written in TypeScript on Deno. It is not a coding agent. +It is the thing a coding agent drives, and it ships first-class skills for Claude Code, +Cursor, OpenCode and Codex so all four can call it. moshcode now installs it as a tool: +`moshcode install swamp`. + +It is worth a PRD because swamp has already solved three problems moshcode keeps paying +for. + +The first is liveness. moshcode decides whether an engine is working, blocked or gone by +matching regular expressions against what the engine printed. `src/engines.mjs` carries +per-engine `state` patterns for permission prompts and trust dialogs, and the herd reads +pane titles. Every one of those is a guess about another vendor's UI, and the guesses +expire. Claude Code 2.1 overwrote the pane title and dropped "? for shortcuts" from its +footer, and detection broke. It will break again, because nothing in that arrangement is +a contract. Swamp does not guess. It keeps a local SQLite run tracker and a run is alive +because it sent a heartbeat. + +The second is the record. moshcode writes an OpenFleet ledger and parses engine +transcripts in `src/cost.mjs`, but there is no single immutable, versioned, searchable +answer to "what ran, with what inputs, producing what". Swamp records every run that way +by default, inputs and outputs and every step between. + +The third is orchestration shape. `moshscript` (PRD 0004) is a script and `moshcode +swarm` (PRD 0015) is a flat fan-out into phases. Swamp's workflows are DAGs with +dependency resolution, which is where its parallelism comes from: it does not need to be +told what can run at once because the graph already says so. + +There is also a smaller lesson worth writing down. Swamp has four configuration layers +with a documented precedence: repo, user, environment, CLI. moshcode has `~/.moshcode`, +synconfig, and environment overrides such as `MOSHCODE_ENGINE_BIN_*`, with no written +order. PR #535 existed to isolate the test suite from those overrides, which is the bill +for leaving precedence undocumented. + +## Goals + +- An engine's state is reported because the run said so, not because its footer still + matches a pattern we wrote months ago. +- A run's inputs, outputs and steps are recoverable afterwards, immutably, without + re-parsing a vendor transcript. +- A swarm expresses what depends on what, and gets its parallelism from that rather than + from a phase count chosen by hand. +- `moshcode doctor` answers "is this installation actually healthy" in one command. +- Configuration precedence is written down and tested. +- Ideas are taken, code is not. See Non-Goals. + +## Non-Goals + +- Vendoring or copying swamp's source. Swamp is AGPL-3.0 with a Swamp Extension and + Definition Exception. moshcode is MIT. Every requirement here is an independently + implemented idea, and no swamp code, schema file or extension is to be copied into this + repo. This is the binding constraint on the whole PRD. +- A Deno runtime. moshcode is Node and ESM per the house stack. +- Replacing moshscript or swarm. This reshapes what swarm records and how it orders work. + It does not add a second orchestrator. +- Re-proposing the credential vault. Swamp injects credentials at runtime so they never + reach a prompt, which is the same requirement as PRD 0018 R8 arriving from a second + direction. It stays in 0018. That two unrelated tools converged on it is the argument + for building it, not for writing it twice. +- Swamp's hosted side. Serve, fleet tokens and the club account are theirs. moshcode + wraps the local CLI. + +## Users + +- The sysop whose herd says an engine is idle when it is waiting on a permission prompt, + because that engine shipped a new footer last week. +- The human asking, a day later, what a swarm actually did and with which inputs. +- Anyone debugging an install where the binary is present but not on PATH, or where the + checkout is twenty commits behind `~/.moshcode/pkg`. + +## Requirements + +- R1 [P0] Swamp is installable as a moshcode tool, with its non-interactive flag set so a + piped install never stalls on the vendor's signup prompt, and with every non-root + landing directory searched. Done in this change. +- R2 [P0] Heartbeat liveness. A moshcode-started run records a heartbeat, and herd state + is derived from it first. Output pattern matching stays as a fallback for engines that + cannot report, and is labelled as inference rather than fact. +- R3 [P0] A run record is immutable and versioned: inputs, the engine and model, every + step, and outputs. It extends the OpenFleet ledger rather than opening a second store. +- R4 [P1] Run history is searchable from the CLI, the TUI and the MCP bridge, per the + house agent-surfaces rule. Any dashboard is hqtui, not a localhost web page. +- R5 [P1] Swarm pieces declare dependencies, and the runner derives concurrency from the + resulting DAG instead of from a fixed phase list. A piece with no dependencies starts + immediately. +- R6 [P1] `moshcode doctor` checks the things that have actually gone wrong before: binary + present but not on PATH, checkout behind the installed package, an engine bin override + in the environment, a half-finished DNS enable, and a stale config. It exits non-zero on + a real fault and says what to run next. +- R7 [P1] Configuration precedence is documented and tested: repo, user, environment, CLI, + in that order, with `moshcode doctor` able to print which layer won for a given key. +- R8 [P1] Skills install to `.agents/skills` alongside the per-vendor paths, so a skill + moshcode installs is visible to any agent that adopts the neutral convention. Extends + PRD 0003. +- R9 [P2] `moshcode audit` reviews command history: what was run, by whom or by which + agent, and what it touched. +- R10 [P2] Plugins carry a quality signal, so `moshcode plugin discover` can rank rather + than only list. Extends PRD 0008. +- R11 [P2] A generated moshscript can be validated against a schema before it runs, so a + script an agent wrote fails at validation rather than halfway through execution. +- R12 [P0] Copy follows the house standard. Short sentences, no em dashes. + +## UX Notes + +Liveness is the requirement that changes what the herd feels like, and it has to degrade +honestly. An engine that reports a heartbeat is shown as known. An engine whose state was +inferred from its output is shown as inferred. Today both look identical and one of them +is frequently wrong, which is worse than either. + +`doctor` should be boring and specific. Not a score, not a spinner. A list of checks with +a verdict each, and for anything that fails, the exact command that fixes it. Every check +in R6 is drawn from a failure that has already happened here at least once. + +Swamp is a tool, not an engine, and the help wall should keep it that way. `moshcode +agents` lists things you can land a herd pane on and start talking to. Swamp has no +interactive session, so it lives with railway, gh and supabase instead. + +## Success Metrics + +- A vendor UI change no longer produces a wrong herd state for engines that heartbeat. +- Every swarm run is reconstructable from its record alone, without the original + transcripts. +- Swarm wall-clock time on a task with independent pieces improves against the current + phase-ordered run. +- `moshcode doctor` reproduces at least four previously-hit failures as failing checks, + on purpose, in tests. +- Precedence has a test that pins the order, so PR #535's isolation problem cannot recur + silently. + +## Risks & Open Questions + +- Heartbeats only exist for runs moshcode starts. An engine launched by hand in a pane + still has to be inferred, so the two-tier display in UX Notes is permanent rather than + transitional. +- An immutable run record grows without bound. It needs a retention story before it + ships, not after. omp's `gc` with per-directory retention is the shape to copy. +- A DAG is more expressive than the current phase list, which means a planner can now + author a cycle or a deadlock. The runner must reject a cyclic graph up front with the + cycle named. +- The license boundary is real and easy to cross by accident, particularly for anyone + reading swamp's YAML schemas while implementing R5 or R11. Requirements here describe + behaviour on purpose and name no swamp file. If an implementation needs their schema, + that is the moment to stop and take legal advice instead. +- Open question: does the run record belong in SQLite like swamp's, or in the JSON and + append-only files OpenFleet already uses? SQLite buys search cheaply. Files keep the + "no daemon, no database, one host" property PRD 0016 chose deliberately. Deciding this + is the first task, because R3 and R4 both depend on it. +- Open question: `.agents/skills` is a convention, not a standard. Worth checking whether + anything beyond swamp reads it before treating it as a destination. diff --git a/prd/README.md b/prd/README.md index ce2f1de5..037847c0 100644 --- a/prd/README.md +++ b/prd/README.md @@ -34,4 +34,5 @@ Start one with `moshcode prd ""` (TUI: `/prd`). | [0016](0016-openfleet-the-record-a-swarm-leaves-behind.md) | OpenFleet: the record a swarm leaves behind, and the fleet verb that reads it | Draft | | [0017](0017-moshcode-on-the-omarchy-bar.md) | Put the herd on the Omarchy bar — a plugin, and the one snapshot it reads | Accepted | | [0018](0018-take-what-omp-got-right.md) | Take what omp got right: cross-engine handoff, stream rules, and a usage ledger | Draft | +| [0019](0019-take-what-swamp-got-right.md) | Take what swamp got right: heartbeat liveness, immutable run history, and workflow DAGs | Draft | diff --git a/src/tools.mjs b/src/tools.mjs index 282fe1ea..4999e9dd 100644 --- a/src/tools.mjs +++ b/src/tools.mjs @@ -330,6 +330,44 @@ export const TOOLS = { // yt-dlp have. binDirs: [path.join(homedir(), ".local", "bin")], }, + swamp: { + desc: "Swamp — deterministic automation: typed models, workflow DAGs, versioned runs, and a runtime credential vault", + bin: "swamp", + // The step up from noodle: noodle runs one HTTP request, swamp runs a DAG of + // model methods with dependency ordering, records every run immutably, and + // injects credentials from a vault at execution time so they never reach a + // prompt. System Initiative builds it (github.com/systeminit/swamp, + // TypeScript on Deno, AGPL-3.0 with an extension/definition exception). + // + // A tool, not an engine, and the distinction is load-bearing: swamp is + // driven BY a coding agent rather than being one. It ships skills into + // .claude/skills, .cursor/skills and .agents/skills so Claude Code, Cursor, + // OpenCode and Codex can all call it. There is no interactive session to + // land a herd pane on, so `moshcode agents` is the wrong list for it. + // + // SWAMP_NONINTERACTIVE is not optional here. The vendor script prompts for + // SWAMP CLUB signup partway through, and a piped install under moshcode has + // no one to answer it; the script honours CI too, but saying the intent + // outright beats pretending to be a build. + install: { + cmd: "sh", + args: ["-c", "curl -fsSL https://swamp-club.com/install.sh | SWAMP_NONINTERACTIVE=1 sh"], + }, + // No `upgrade` key: no vendor updater is documented, and the installer + // always pulls the `stable` release, so re-running it IS the upgrade and + // toolUpgradeSpec falls back to install on its own — same as noodle above. + // + // Install target depends on who runs it: root gets /usr/local/bin, a normal + // user gets ~/.local/bin or ~/bin when either is already on PATH, and + // ~/.swamp/bin when neither is. Only that last one is a directory nothing + // else here adds, and the script appends to no shell rc, so all three + // non-root landing spots are searched. + binDirs: [ + path.join(homedir(), ".local", "bin"), + path.join(homedir(), "bin"), + path.join(homedir(), ".swamp", "bin"), + ], + }, spinifex: { desc: "Spinifex — AWS-compatible cloud on your own hardware (EC2, EBS, S3, VPC, IAM)", // The product is Spinifex; the binary it installs is `spx`. Same split as