Files
poimen/tasks/INDEX.md
T

364 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/<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:
```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.8T3.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 | ⬜ |