21 KiB
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:
- Both front doors canonicalize to the same IR. The same workflow written
as YAML and built with
WorkflowDef::builder()produces a byte-identicalWorkflowVersion. §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. - Unresolved ids fail at load, never at spawn. YAML naming a
VerifierId,JudgeIdorModelIdno 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. - 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/<task>.sh # runs the task's Command, prints one line per assertion
verify/expected/<task>.txt # the exact output that script must produce
2. Numbered assertion N in the Verify section is test fn aN_<slug>. 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:
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 | — | — |
| 11 | Orchestration | T10.x | 10 | 0 | 0 | 10 | ⬜ T10.10 | — | — |
| Total | 90 | 3 | 0 | 87 | 0/11 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 | Identity newtypes | S | — | ✅ |
| T0.2 | Kernel AttemptState |
S | — | ✅ |
| T0.3 | WorkEvent and SchemaVersion |
M | — | ✅ |
| T0.4 | Upcaster framework | M | — | ⬜ |
| T0.5 | EventLog port + redb implementation |
L | — | ⬜ |
| T0.6 | Atomic commit protocol | M | — | ⬜ |
| T0.7 | BlobStore port + redb implementation |
M | — | ⬜ |
| T0.8 | Fold and re-derive | M | — | ⬜ |
| T0.9 | Foundations composition gate | M | gate | ⬜ |
2 — Walking skeleton · T1.x
| Task | Title | Size | Flags | Status |
|---|---|---|---|---|
| T1.1 | Ctx and RunScope |
M | — | ⬜ |
| T1.2 | Stub model provider | S | — | ⬜ |
| T1.3 | Run executor | L | — | ⬜ |
| T1.4 | Attempt lifecycle and retry | M | — | ⬜ |
| T1.5 | Context partition capture | S | — | ⬜ |
| T1.6 | Prompt and output blob capture | S | — | ⬜ |
| T1.7 | Episode query surface | M | — | ⬜ |
| T1.8 | 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 | WorkflowDef IR + canonicalization |
L | — | ⬜ |
| T3.2 | WorkflowFormat trait + YAML and JSON |
M | — | ⬜ |
| T3.8 | WorkflowDef builder — the Rust front door |
M | new | ⬜ |
| T3.9 | Capability registry — ids YAML can name | M | new | ⬜ |
| T3.3 | Load-time validation | M | — | ⬜ |
| T3.4 | StepId stability checks |
S | — | ⬜ |
| T3.5 | Interpreter over the IR | L | — | ⬜ |
| T3.6 | Version pinning at spawn | S | — | ⬜ |
| T3.10 | Front-door equivalence — YAML ≡ builder | M | new | ⬜ |
| T3.7 | Surface composition gate | M | gate | ⬜ |
4 — Verification · T4.x
| Task | Title | Size | Flags | Status |
|---|---|---|---|---|
| T4.1 | Verifier port |
S | — | ⬜ |
| T4.2 | VerifierCtx and the snapshot barrier |
M | — | ⬜ |
| T4.3 | Lazy blob access | S | — | ⬜ |
| T4.4 | Retention ordering guard | S | — | ⬜ |
| T4.5 | 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 | poimen-sdk — ports without the engine |
M | new | ⬜ |
| T9.2 | Sidecar protocol — JSON-RPC 2.0 over stdio | L | new | ⬜ |
| T9.3 | poimen serve --stdio |
M | new | ⬜ |
| T9.4 | Protocol conformance suite | L | new | ⬜ |
| T9.5 | dsh reference plugin |
M | new | ⬜ |
| T9.6 | Embedding composition gate | L | gate | ⬜ |
6 — Durability hard parts · T2.x
| Task | Title | Size | Flags | Status |
|---|---|---|---|---|
| T2.1 | Write-ahead intent | L | — | ⬜ |
| T2.2 | Effect-class recovery | M | — | ⬜ |
| T2.3 | Rewind as fork | L | — | ⬜ |
| T2.4 | Schema evolution end-to-end | M | — | ⬜ |
| T2.5 | Checkpoints | M | — | ⬜ |
| T2.6 | Crash matrix | L | — | ⬜ |
| T2.7 | Durability composition gate | L | gate | ⬜ |
7 — Grading · T5.x
| Task | Title | Size | Flags | Status |
|---|---|---|---|---|
| T5.1 | TaskId at spawn |
S | — | ⬜ |
| T5.2 | CapacityLimits and the residency invariant |
M | — | ⬜ |
| T5.3 | EvaluationStrategy port + ResourceProfile |
S | — | ⬜ |
| T5.4 | DeterministicGrader |
S | — | ⬜ |
| T5.5 | PairwiseSequential: reference and comparison |
M | — | ⬜ |
| T5.6 | Sequential test and stopping | M | — | ⬜ |
| T5.7 | Order alternation and sampled consistency | S | — | ⬜ |
| T5.8 | Swiss pairing | M | opt-in | ⬜ |
| T5.9 | Bradley-Terry fit | L | opt-in | ⬜ |
| T5.10 | Attempt tournaments | M | — | ⬜ |
| T5.11 | Degradation reasons | M | — | ⬜ |
| T5.12 | Grading composition gate | L | gate | ⬜ |
8 — Learning loop · T6.x
| Task | Title | Size | Flags | Status |
|---|---|---|---|---|
| T6.1 | Challenger allocation | M | — | ⬜ |
| T6.2 | Judge calibration set | M | — | ⬜ |
| T6.3 | Promotion gates | M | — | ⬜ |
| T6.4 | Per-variant aggregation | M | opt-in | ⬜ |
| T6.5 | Sandboxed replay | L | — | ⬜ |
| T6.6 | Held-out split | M | — | ⬜ |
| T6.7 | Drift check | S | — | ⬜ |
| T6.8 | Learning composition gate | L | gate | ⬜ |
9 — Distribution · T7.x
| Task | Title | Size | Flags | Status |
|---|---|---|---|---|
| T7.1 | Postgres EventLog |
L | — | ⬜ |
| T7.2 | Object-store BlobStore |
M | — | ⬜ |
| T7.3 | Leases and fencing | L | — | ⬜ |
| T7.4 | Outbox relay | M | — | ⬜ |
| T7.5 | Partition keys on adapters | S | — | ⬜ |
| T7.6 | Tournament as a join stage | L | — | ⬜ |
| T7.7 | turmoil suite |
L | parallel-ok | ⬜ |
| T7.8 | Distribution composition gate | L | gate | ⬜ |
10 — Operability · T8.x
| Task | Title | Size | Flags | Status |
|---|---|---|---|---|
| T8.1 | Metering | M | — | ⬜ |
| T8.2 | Metrics | M | — | ⬜ |
| T8.3 | Keyed capability | L | — | ⬜ |
| T8.4 | Reduction and tiering | L | — | ⬜ |
| T8.5 | Embedded-mode smoke | S | parallel-ok | ⬜ |
| T8.6 | Capacity admission control | M | — | ⬜ |
| T8.7 | Operability composition gate | L | gate | ⬜ |
11 — Orchestration · T10.x
Fills §17's deferred line — multi-agent orchestration, revisited now that
cross-agent coordination is real. A Reconciler port picks up a run at its
existing Verified/Graded/Ungraded resting states, decides what runs
next toward a GoalId-scoped objective, and dispatches through Kafka via
the same outbox/inbox discipline T7.4 established — never a direct spawn
call. An HTTP surface binds one method table shared with T9.2's eventual
stdio transport. Two front doors, one IR: tasks/*.md parsed directly
(door a), or a free-text problem statement drafted and parked in
Suspended for human approval before it runs (door b).
Depends on T1.x, T2.1/T2.3, T3.x, T7.3/T7.4, T9.1 landing first; not blocked on T9.2–T9.6 (the stdio binding develops in parallel against the same method table T10.4 authors).
| Task | Title | Size | Flags | Status |
|---|---|---|---|---|
| T10.1 | GoalId and goal-scoped query |
S | — | ⬜ |
| T10.2 | Reconciler port |
M | — | ⬜ |
| T10.3 | Idempotent run admission | S | — | ⬜ |
| T10.4 | HTTP API surface | L | — | ⬜ |
| T10.5 | TaskBoardFormat plugin |
M | — | ⬜ |
| T10.6 | plan-draft workflow + Suspended-approval flow |
M | — | ⬜ |
| T10.7 | Kafka topics and inbox relay | L | — | ⬜ |
| T10.8 | Orchestrator reference service | M | — | ⬜ |
| T10.9 | CLI: runs list/episode/transcript | S | — | ⬜ |
| T10.10 | Orchestration composition gate | L | gate | ⬜ |