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