# poimen — task board 80 tasks — 70 build tasks plus **10 composition gates**, one per phase. One file per task, **self-contained**: inlined design facts, executable steps, acceptance criteria, a `Verify` section written for someone who did not build the thing, and the traps worth naming. Reading `rust-agentic-sys.md` is not required to do a task — it is linked as background only. Each `Verify` section names the harness, the integration test with numbered assertions, the command to run, and the **false pass** — the shape of test that goes green while the feature is broken. Treat the false-pass list as part of the acceptance criteria, not commentary. poimen is a **pluggable unit**. It is embedded by other harnesses, not run as an application. Two things follow, and they are the spine of this board: the public surface is built early and frozen deliberately, and every phase after it is judged partly on whether it broke that surface. ## The customization contract — two front doors, one IR poimen is customized two ways, and they are different axes rather than alternatives. Confusing them is how a project ends up with two products. ``` workflow.yaml ─────────┐ ├──► WorkflowDef ──► Blake3 canonical hash ──► WorkflowVersion WorkflowDef::builder() ┘ ▲ │ referenced by id impl Verifier / Judge / ModelProvider / storage ports ``` **YAML declares. Rust implements.** - **YAML** names steps, transitions, verifier ids, judge ids, retry policy, budgets, rubrics. It is data. It ships without a compiler and is the surface a user touches to change *what runs*. - **Rust** supplies behaviour behind the ports and registers it under an id. It is code. It is the surface a user touches to change *how something works*. Neither is a superset. A YAML file can only name capabilities some Rust impl registered; a Rust impl is inert until some workflow names it. Three rules the board enforces, each owned by a task: 1. **Both front doors canonicalize to the same IR.** The same workflow written as YAML and built with `WorkflowDef::builder()` produces a byte-identical `WorkflowVersion`. §4.1 hashes the canonicalized IR, not the source text, so this is testable — and if it ever fails, §12's "did this change affect results" silently returns nonsense. T3.10 owns it and the P-surface gate re-checks it. 2. **Unresolved ids fail at load, never at spawn.** YAML naming a `VerifierId`, `JudgeId` or `ModelId` no impl registered is a load error naming the id and the file. A workflow that parses and then dies mid-run on a missing verifier has moved a config error into production. T3.9 owns the registry; T3.3 owns the failure. 3. **Neither door reaches past the ports.** A YAML key that only one storage backend understands, or a builder method that assumes `redb`, breaks embedding. The gates check both doors against both storage modes. ## The embedding contract — sidecar, JSON-RPC over stdio poimen is embedded by harnesses written in other languages; DeepSeek Harness (`dsh`, TypeScript) is the reference consumer. The boundary is a **child process speaking JSON-RPC 2.0 over stdio**, not a native addon. ``` ┌──────────────┐ spawn ┌───────────────────┐ │ host harness │──────────► │ poimen serve │ │ any language │ ◄─stdio──► │ own process │ └──────────────┘ JSON-RPC └───────────────────┘ ``` Chosen because it containerizes with no change, hosts in any language, and keeps a Rust panic out of the host's crash domain. The cost is a serialization boundary and a protocol that must be versioned like any other wire format — so it is versioned like one, with the same discipline §8.7 applies to the log: methods are added, never repurposed; fields are added, never removed. **The protocol is a published artifact, not an implementation detail.** A change to it is a breaking change to every embedder, and the conformance suite (T9.4) is what makes that statement enforceable rather than aspirational. An in-process Rust API exists for Rust embedders and is the same surface; the sidecar is that surface with a wire format in front of it. An HTTP transport over the identical method set is the natural pod deployment and is deferred, not designed away. ## Ordering — declared, never derived **Phase order is the list below. Task ids are opaque and frozen.** The board reorders as understanding changes; task ids do not move when it does. `T3.1` is `T3.1` forever, in whatever phase it currently sits, because its artifacts, its cost-ledger rows and every cross-reference key on that id. New tasks take new ids rather than renumbering their neighbours. This is §4.3's `StepId` rule applied to the board itself: *"insert a step at position 2 and every positional index shifts, but `StepId` does not… never parsed, never ordered, never assumed numeric."* A board that renumbers to reorder has the bug it warns its own users about. Consequence: **id order is not execution order.** Read the phase list, not the filenames. Ordering rule: no phase starts until its predecessor's gate is green. The `gate` task of each phase **is** that gate — a required CI job proving the phase's tasks compose and that its swappable parts are genuinely swappable. Every build task is verified alone; the gate verifies the properties no single task owns. `opt-in` tasks ship disabled and gate nothing. - **Engineering Quality Rule:** All implementations must strictly adhere to the idiomatic, zero-copy, and type-safe architecture standards defined in `rust-guide-line.md`. - **Agent Output Rule — caveman full.** Every agent on this board writes caveman full. Applies to prose only. Drop: articles (a/an/the), filler (just/really/basically/actually/simply), pleasantries (sure/certainly/happy to), hedging (might be worth/you could consider), connectives (however/furthermore/additionally), preamble, recap, tool-call narration, restating these rules back. Fragments OK. Short synonyms — big not extensive, fix not "implement a solution for". Preserve exactly, never abbreviate: code blocks, inline code, file paths, commands, error strings, test names, crate and API names, version numbers, env vars, URLs. Never invent abbreviations (cfg/impl/req/fn) — they tokenize the same as the full word and read worse. Drop caveman where compression creates ambiguity: multi-step ordering, destructive-operation warnings, anything a misread would break. Pattern: `[thing] [action] [reason]. [next step].` Not: "I'll go ahead and implement the transition function for you, which..." Yes: "transition() exhaustive match, no catch-all. Terminal states reject all." ## Verification practice — script first, source second **A task is verified by running a script and diffing its output. Reading the source happens after that diff is clean, never instead of it.** Reviewing a diff first is how an assertion that was quietly dropped still gets called done: the code looks right, and nothing proves the test ran. Every task already carries the four things this needs — a `Harness`, an `Integration test` with numbered assertions, a `Command`, and a `False pass` list. The practice is to make them executable rather than descriptive. **1. One script per task, committed with it.** ``` verify/.sh # runs the task's Command, prints one line per assertion verify/expected/.txt # the exact output that script must produce ``` **2. Numbered assertion N in the Verify section is test fn `aN_`.** The numbering is the contract. `tests/it_scoped_keys.rs` assertion 4 is `a4_scan_the_raw_table`, and the script reports it by name: ``` it_scoped_keys::a1_open_one_table_keyed_scoped_runid PASS it_scoped_keys::a4_scan_the_raw_table MISSING ``` `MISSING` is the point. A test fn that does not exist reports missing instead of being absent from a green summary — the failure mode where `cargo test` says `ok` because the assertion was never written. **3. The expected file is the review artifact.** The script's output is diffed against it. An empty diff is the only pass. A changed expected file in a diff is a claim that the task's Verify section changed, and gets read as one. **4. The `False pass` list is the checklist applied *after* the diff is clean,** not before. Every line saying `PASS` is exactly the state those traps are written to survive — that is when a reviewer opens the source, and the false-pass list is what they open it with. **5. Gate tasks follow the same rule, at phase scope.** The gate's script covers the properties no single task owns; a phase is green when its gate script's diff is empty, not when its member tasks individually passed. **6. No crate, no verification.** Until a `Cargo.toml` exists the scripts report blocked rather than passing vacuously. A verification step that cannot fail is not a verification step. **7. Surface tasks are verified through both front doors.** Any task in P-surface or later that touches workflow definition asserts the YAML path and the builder path independently, then asserts their hashes match. One path verified is half a feature. ## Progress **Source of truth is the `Status` field in each task file.** The tables below mirror it; a status changed here and not there is a lie. Regenerate the mirror: ```sh for f in T*.md; do printf '%s\t%s\n' "${f%%-*}" "$(sed -n 's/^| Status | *\(.*[^ ]\) *|$/\1/p' "$f" | head -1)" done | sort -t. -k1,1 -k2,2n ``` Legend: ⬜ not started · 🟡 in progress · ✅ done · ⛔ blocked | # | Phase | Ids | Tasks | ✅ | 🟡 | ⬜ | Gate | Tokens | Cost | |---|---|---|---|---|---|---|---|---|---| | 1 | Foundations | T0.x | 9 | 3 | 0 | 6 | ⬜ T0.9 | 313.0k | — | | 2 | Walking skeleton | T1.x | 8 | 0 | 0 | 8 | ⬜ T1.8 | — | — | | 3 | Public surface | T3.x | 10 | 0 | 0 | 10 | ⬜ T3.7 | — | — | | 4 | Verification | T4.x | 5 | 0 | 0 | 5 | ⬜ T4.5 | — | — | | 5 | Embedding | T9.x | 6 | 0 | 0 | 6 | ⬜ T9.6 | — | — | | 6 | Durability hard parts | T2.x | 7 | 0 | 0 | 7 | ⬜ T2.7 | — | — | | 7 | Grading | T5.x | 12 | 0 | 0 | 12 | ⬜ T5.12 | — | — | | 8 | Learning loop | T6.x | 8 | 0 | 0 | 8 | ⬜ T6.8 | — | — | | 9 | Distribution | T7.x | 8 | 0 | 0 | 8 | ⬜ T7.8 | — | — | | 10 | Operability | T8.x | 7 | 0 | 0 | 7 | ⬜ T8.7 | — | — | | | **Total** | | **80** | **3** | **0** | **77** | 0/10 green | **313.0k** | **—** | **What changed in this revision.** The public surface moved ahead of the durability hard parts, and an embedding phase was added after verification. Reason: pluggable-first. Under the previous order nobody could write a plugin or embed poimen until 36 tasks in, which meant the surface others depend on would have been designed with no user and validated with none either. The cost that buys: the log format can still churn under a published surface. That is what T0.4's upcasters are insurance for — §8.7, versioned records, upcasters on read, variants never removed — and it is why the durability phase sits immediately after embedding rather than last. ## 1 — Foundations · T0.x | Task | Title | Size | Flags | Status | |---|---|---|---|---| | [T0.1](T0.1-identity-newtypes.md) | Identity newtypes | S | — | ✅ | | [T0.2](T0.2-kernel-attemptstate.md) | Kernel `AttemptState` | S | — | ✅ | | [T0.3](T0.3-workevent-and-schemaversion.md) | `WorkEvent` and `SchemaVersion` | M | — | ✅ | | [T0.4](T0.4-upcaster-framework.md) | Upcaster framework | M | — | ⬜ | | [T0.5](T0.5-eventlog-port-redb-implementation.md) | `EventLog` port + `redb` implementation | L | — | ⬜ | | [T0.6](T0.6-atomic-commit-protocol.md) | Atomic commit protocol | M | — | ⬜ | | [T0.7](T0.7-blobstore-port-redb-implementation.md) | `BlobStore` port + `redb` implementation | M | — | ⬜ | | [T0.8](T0.8-fold-and-re-derive.md) | Fold and re-derive | M | — | ⬜ | | [T0.9](T0.9-p0-composition-gate.md) | **Foundations composition gate** | M | gate | ⬜ | ## 2 — Walking skeleton · T1.x | Task | Title | Size | Flags | Status | |---|---|---|---|---| | [T1.1](T1.1-ctx-and-runscope.md) | `Ctx` and `RunScope` | M | — | ⬜ | | [T1.2](T1.2-stub-model-provider.md) | Stub model provider | S | — | ⬜ | | [T1.3](T1.3-run-executor.md) | Run executor | L | — | ⬜ | | [T1.4](T1.4-attempt-lifecycle-and-retry.md) | Attempt lifecycle and retry | M | — | ⬜ | | [T1.5](T1.5-context-partition-capture.md) | Context partition capture | S | — | ⬜ | | [T1.6](T1.6-prompt-and-output-blob-capture.md) | Prompt and output blob capture | S | — | ⬜ | | [T1.7](T1.7-episode-query-surface.md) | Episode query surface | M | — | ⬜ | | [T1.8](T1.8-p1-composition-gate.md) | **Skeleton composition gate** | M | gate | ⬜ | ## 3 — Public surface · T3.x The customization contract, built once and then defended by every later gate. Execution order within the phase is the row order below — note it is not id order, because T3.8–T3.10 were added after the originals and the gate stays last. | Task | Title | Size | Flags | Status | |---|---|---|---|---| | [T3.1](T3.1-workflowdef-ir-canonicalization.md) | `WorkflowDef` IR + canonicalization | L | — | ⬜ | | [T3.2](T3.2-workflowformat-trait-yaml-and-json.md) | `WorkflowFormat` trait + YAML and JSON | M | — | ⬜ | | [T3.8](T3.8-workflowdef-builder.md) | `WorkflowDef` builder — the Rust front door | M | new | ⬜ | | [T3.9](T3.9-capability-registry.md) | Capability registry — ids YAML can name | M | new | ⬜ | | [T3.3](T3.3-load-time-validation.md) | Load-time validation | M | — | ⬜ | | [T3.4](T3.4-stepid-stability-checks.md) | `StepId` stability checks | S | — | ⬜ | | [T3.5](T3.5-interpreter-over-the-ir.md) | Interpreter over the IR | L | — | ⬜ | | [T3.6](T3.6-version-pinning-at-spawn.md) | Version pinning at spawn | S | — | ⬜ | | [T3.10](T3.10-front-door-equivalence.md) | Front-door equivalence — YAML ≡ builder | M | new | ⬜ | | [T3.7](T3.7-p3-composition-gate.md) | **Surface composition gate** | M | gate | ⬜ | ## 4 — Verification · T4.x | Task | Title | Size | Flags | Status | |---|---|---|---|---| | [T4.1](T4.1-verifier-port.md) | `Verifier` port | S | — | ⬜ | | [T4.2](T4.2-verifierctx-and-the-snapshot-barrier.md) | `VerifierCtx` and the snapshot barrier | M | — | ⬜ | | [T4.3](T4.3-lazy-blob-access.md) | Lazy blob access | S | — | ⬜ | | [T4.4](T4.4-retention-ordering-guard.md) | Retention ordering guard | S | — | ⬜ | | [T4.5](T4.5-p4-composition-gate.md) | **Verification composition gate** | M | gate | ⬜ | ## 5 — Embedding · T9.x Makes poimen a unit another harness mounts. The reference consumer is `dsh`, whose architecture already expects capabilities to arrive as swappable providers — so if the sidecar cannot be mounted as one, the boundary is wrong, not `dsh`. | Task | Title | Size | Flags | Status | |---|---|---|---|---| | [T9.1](T9.1-poimen-sdk-crate.md) | `poimen-sdk` — ports without the engine | M | new | ⬜ | | [T9.2](T9.2-sidecar-protocol.md) | Sidecar protocol — JSON-RPC 2.0 over stdio | L | new | ⬜ | | [T9.3](T9.3-serve-stdio.md) | `poimen serve --stdio` | M | new | ⬜ | | [T9.4](T9.4-protocol-conformance-suite.md) | Protocol conformance suite | L | new | ⬜ | | [T9.5](T9.5-dsh-reference-plugin.md) | `dsh` reference plugin | M | new | ⬜ | | [T9.6](T9.6-embedding-composition-gate.md) | **Embedding composition gate** | L | gate | ⬜ | ## 6 — Durability hard parts · T2.x | Task | Title | Size | Flags | Status | |---|---|---|---|---| | [T2.1](T2.1-write-ahead-intent.md) | Write-ahead intent | L | — | ⬜ | | [T2.2](T2.2-effect-class-recovery.md) | Effect-class recovery | M | — | ⬜ | | [T2.3](T2.3-rewind-as-fork.md) | Rewind as fork | L | — | ⬜ | | [T2.4](T2.4-schema-evolution-end-to-end.md) | Schema evolution end-to-end | M | — | ⬜ | | [T2.5](T2.5-checkpoints.md) | Checkpoints | M | — | ⬜ | | [T2.6](T2.6-crash-matrix.md) | Crash matrix | L | — | ⬜ | | [T2.7](T2.7-p2-composition-gate.md) | **Durability composition gate** | L | gate | ⬜ | ## 7 — Grading · T5.x | Task | Title | Size | Flags | Status | |---|---|---|---|---| | [T5.1](T5.1-taskid-at-spawn.md) | `TaskId` at spawn | S | — | ⬜ | | [T5.2](T5.2-capacitylimits-and-the-residency-invariant.md) | `CapacityLimits` and the residency invariant | M | — | ⬜ | | [T5.3](T5.3-evaluationstrategy-port-resourceprofile.md) | `EvaluationStrategy` port + `ResourceProfile` | S | — | ⬜ | | [T5.4](T5.4-deterministicgrader.md) | `DeterministicGrader` | S | — | ⬜ | | [T5.5](T5.5-pairwisesequential-reference-and-comparison.md) | `PairwiseSequential`: reference and comparison | M | — | ⬜ | | [T5.6](T5.6-sequential-test-and-stopping.md) | Sequential test and stopping | M | — | ⬜ | | [T5.7](T5.7-order-alternation-and-sampled-consistency.md) | Order alternation and sampled consistency | S | — | ⬜ | | [T5.8](T5.8-swiss-pairing.md) | Swiss pairing | M | opt-in | ⬜ | | [T5.9](T5.9-bradley-terry-fit.md) | Bradley-Terry fit | L | opt-in | ⬜ | | [T5.10](T5.10-attempt-tournaments.md) | Attempt tournaments | M | — | ⬜ | | [T5.11](T5.11-degradation-reasons.md) | Degradation reasons | M | — | ⬜ | | [T5.12](T5.12-p5-composition-gate.md) | **Grading composition gate** | L | gate | ⬜ | ## 8 — Learning loop · T6.x | Task | Title | Size | Flags | Status | |---|---|---|---|---| | [T6.1](T6.1-challenger-allocation.md) | Challenger allocation | M | — | ⬜ | | [T6.2](T6.2-judge-calibration-set.md) | Judge calibration set | M | — | ⬜ | | [T6.3](T6.3-promotion-gates.md) | Promotion gates | M | — | ⬜ | | [T6.4](T6.4-per-variant-aggregation.md) | Per-variant aggregation | M | opt-in | ⬜ | | [T6.5](T6.5-sandboxed-replay.md) | Sandboxed replay | L | — | ⬜ | | [T6.6](T6.6-held-out-split.md) | Held-out split | M | — | ⬜ | | [T6.7](T6.7-drift-check.md) | Drift check | S | — | ⬜ | | [T6.8](T6.8-p6-composition-gate.md) | **Learning composition gate** | L | gate | ⬜ | ## 9 — Distribution · T7.x | Task | Title | Size | Flags | Status | |---|---|---|---|---| | [T7.1](T7.1-postgres-eventlog.md) | Postgres `EventLog` | L | — | ⬜ | | [T7.2](T7.2-object-store-blobstore.md) | Object-store `BlobStore` | M | — | ⬜ | | [T7.3](T7.3-leases-and-fencing.md) | Leases and fencing | L | — | ⬜ | | [T7.4](T7.4-outbox-relay.md) | Outbox relay | M | — | ⬜ | | [T7.5](T7.5-partition-keys-on-adapters.md) | Partition keys on adapters | S | — | ⬜ | | [T7.6](T7.6-tournament-as-a-join-stage.md) | Tournament as a join stage | L | — | ⬜ | | [T7.7](T7.7-turmoil-suite.md) | `turmoil` suite | L | parallel-ok | ⬜ | | [T7.8](T7.8-p7-composition-gate.md) | **Distribution composition gate** | L | gate | ⬜ | ## 10 — Operability · T8.x | Task | Title | Size | Flags | Status | |---|---|---|---|---| | [T8.1](T8.1-metering.md) | Metering | M | — | ⬜ | | [T8.2](T8.2-metrics.md) | Metrics | M | — | ⬜ | | [T8.3](T8.3-keyed-capability.md) | Keyed capability | L | — | ⬜ | | [T8.4](T8.4-reduction-and-tiering.md) | Reduction and tiering | L | — | ⬜ | | [T8.5](T8.5-embedded-mode-smoke.md) | Embedded-mode smoke | S | parallel-ok | ⬜ | | [T8.6](T8.6-capacity-admission-control.md) | Capacity admission control | M | — | ⬜ | | [T8.7](T8.7-p8-composition-gate.md) | **Operability composition gate** | L | gate | ⬜ |