Work Units (WUs)
A Work Unit (WU) is a small, well-defined piece of work with clear acceptance criteria and proof of completion.
Why WUs?
Section titled “Why WUs?”Traditional tickets are:
- Vague (“Fix the login bug”)
- Unbounded (no clear definition of done)
- Trust-based (marked done without proof)
WUs are:
- Specific (“Add email validation to login form”)
- Cohesive (one independently shippable and rollbackable outcome)
- Evidence-based (applicable tests and artifacts support the completion decision)
Anatomy of a WU
Section titled “Anatomy of a WU”Canonical WU IDs
Section titled “Canonical WU IDs”Every Work Unit has a canonical id made of a configurable prefix and a
zero-padded integer. Defaults preserve backward compatibility — every existing
Ids use the WU- prefix by default, with no config change.
Format rule
Section titled “Format rule”A canonical id is <prefix><zero-padded-integer> where:
prefixmatches/^[A-Z][A-Z0-9]{1,9}-$/(default'WU-').- the integer body is at least
widthcharacters wide (default4). - the integer body may be wider than
widthonce the sequence overflows (a five-digit body is canonical even whenwidth=4); the sequence never truncates.
wu:create canonicalizes --id input before any file is written, so an unpadded
input is written in the padded form. Auto-generated ids are zero-padded too.
Collision protection
Section titled “Collision protection”Two different string forms of the same integer cannot coexist: a padded and an
unpadded form resolve to the same canonical integer, and wu:create hard-errors if
the integer is already represented anywhere on disk. The error ships with a
copy-pasteable wu:rename remediation command.
Configuration
Section titled “Configuration”Canonical ID behaviour is controlled by software_delivery.wu_id.* in
workspace.yaml:
Default (WU-) example
Section titled “Default (WU-) example”Vendor-neutral prefix example (TASK-)
Section titled “Vendor-neutral prefix example (TASK-)”Teams that prefer a non-WU- prefix can pick a different one without touching any WU source file:
Legacy drift and wu:rename
Section titled “Legacy drift and wu:rename”pnpm lumenflow:doctor --deep flags any WU YAML whose id is valid but not canonical
(e.g. <wu-id>.yaml when width=4). Offenders come with copy-pasteable repair
commands:
wu:rename moves the YAML, rewrites the id: field, moves the .done stamp,
appends a wu_renamed event to .lumenflow/state/packs/software-delivery/wu-events.jsonl, and
regenerates backlog.md and status.md. When a downstream repo has already
canonicalized its YAML out-of-band but the generated views still regenerate
with legacy ids, use --replay-only to append the event without touching the
filesystem:
Either form is append-only — history is never rewritten. Historical events
retain their original wuId; a replay-time projection resolves them to the
new canonical id so generated views only show the post-rename form.
Strict vs permissive mode
Section titled “Strict vs permissive mode”strict: true(default) —lumenflow:doctor --deeptreats drift as exit code 1,wu:rename’s CLI guard refuses a non-canonical--to.strict: false— legacy unpadded ids are tolerated (warning-only). Use this as a migration ramp for repos that cannot normalize in one go; integer-level collision detection still hard-blocks same-integer duplicates.
The State Store: Tracked Rows and Journal-Only Rows
Section titled “The State Store: Tracked Rows and Journal-Only Rows”.lumenflow/state/packs/software-delivery/wu-events.jsonl is tracked in git, and
every landing from every machine rewrites and merges it. It therefore holds
lifecycle state only. Telemetry lives in untracked journals next to it:
| Row | Where it lives | Tracked |
|---|---|---|
claim, complete, release, block and the other lifecycle events, plus the checkpoint proof rows and compact stubs described below | wu-events.jsonl | Yes |
checkpoint free text: the note, progress and next steps of mem:checkpoint, phase checkpoints and wu:edit --notes bodies | checkpoint-journal.jsonl (shared by every worktree of a clone) | No |
gate_attempt, lifecycle_duration | telemetry-journal.jsonl (shared by every worktree of a clone) | No |
heartbeat | heartbeat-journal.jsonl | No |
Pending wu:edit snapshot (the full WU document) | pending-spec-journal.jsonl (local to the claim’s checkout) | No |
The journals are gitignored by the existing state-directory rules, so they never
reach a commit. wu:done stamps a compact gate_summary (attempts, distinct
gates, failures) on the complete event, so first-attempt landing share and the
efficiency ratio read the same on any machine.
Checkpoint rows stay tracked only when another machine or an audit reads them from the ledger: wu:brief evidence (a hash, client and mode), the wu:edit stamp that wu:done recovery re-verifies, overlap authorisations, audited ownership and unread-signal overrides, repair authority stamps and the claim ownership advisory. Every other checkpoint is free progress text: its note, progress and next steps go to the untracked checkpoint journal, so wu:status reads them on this machine and they never inflate the ledger every landing merges. Each such checkpoint still leaves one compact tracked stub (type, wuId, timestamp, the fixed marker note [journal] and the session_id when there is one, about 100 B, no free text), joined to the journal row by wuId and timestamp. The tracked stub is what lets latest-activity readers and the wu:recover restore guard see a newer checkpoint on a fresh clone that has no journal.
A failed wu:prep, a wu:edit and a wu:rebase baseline refresh leave the tracked ledger
unchanged, so wu:rebase never needs the ledger restored by hand.
A ledger that grew before this split can be compacted once:
Recurring ledger pressure cleanup
Section titled “Recurring ledger pressure cleanup”With default operation options, event cleanup checks the tracked ledger at or above 3.5 MiB, below its unchanged 4 MiB shape budget. It can archive redundant proof checkpoints and older ordinary specification updates of completed, stamped WUs. Current state, current brief/edit evidence, and latest activity overall and per session remain available. Active, blocked, ready, unstamped and unknown history, pending edits, audited overrides, amendments, retractions, renames and delegation relationships remain protected. Records that must stay together to preserve a canonical merge result are retained together.
A segment is written only once at least 64 KiB of archivable rows have accumulated, so a landing that retires a few hundred bytes writes no new tracked file. A ledger at or over 8,330,712 B (one floor below the 8,396,248 B proven envelope) archives regardless of the floor.
History stays tracked in monthly, content-addressed raw JSONL segments. Migrated
workspaces support custom archive roots inside the Software Delivery state
namespace. Before migration, cleanup uses the existing legacy defaults, including
.lumenflow/archive; namespace custom overrides take effect after migration.
Archive paths outside the supported repository layout refuse before writing
archives or changing the active ledger. JSON record payloads are preserved; CRLF line delimiters
normalize to LF. Archives become durable before the active ledger is replaced.
A fresh clone receives them with the compact ledger and needs no local journal
to reconstruct current state.
Live audit logs and outbox streams retain union merging. Immutable digest-named
archive segments use -merge to prevent combining different contents, while
existing LF normalization and strict digest checks remain. Initialization and
upgrade propagate the managed attribute rules without replacing user-owned lines
outside the managed block.
Verified committed archives prevent older lanes from restoring retired rows during landing and governed rebases, while retaining unseen concurrent events and genuinely reopened work. Historical brief-hash checks can consult committed archives after a live-ledger miss for a completed, stamped WU in its canonical state directory. Reads are bound to exact source revisions and the actual worktree; invalid revisions, unreadable source data and nonregular archive entries are errors rather than evidence that no archive exists. Ordinary state queries still use the active ledger; ignored local archives and unrelated directories cannot supply historical proof.
Automatic post-Done cleanup publishes only its exact generated segments with the
compact ledger through the normal scope checks, hooks and atomic publication.
For manual cleanup, preview with the command above, then run it without
--dry-run; the report lists the archive paths to publish with the changed
ledger in the owning WU. --compact-ledger remains the separate operation that
moves telemetry to local journals. Callers explicitly supplying archiveAfter
or archiveAfterMs to the event-cleanup operation retain age-based behavior;
these are operation options, not state:cleanup CLI flags.
If cleanup reports conflicting archive bytes or an invalid published digest, preserve the named segment and repair its exact published bytes before retrying. For a committed-source read error, restore access to the named repository and revision before retrying. Do not delete a segment to suppress either error. Cleanup is repeatable after interruption, but the pressure threshold is a trigger rather than a guaranteed final size: protected history stays even when it exceeds the budget, and the shape check can still fail.
WU Lifecycle
Section titled “WU Lifecycle”| Status | Meaning |
|---|---|
ready | Approved, can be claimed |
in_progress | Someone is working on it |
blocked | Waiting on external dependency |
done | Lifecycle completed; stamp created |
Lifecycle signal output
Section titled “Lifecycle signal output”Lifecycle commands reserve stdout for their own result content so scripts and
agent callers can safely pipe or parse it. Coordination-signal notices come
from the shared lifecycle middleware on stderr, with a bounded three-item
preview, the unread count, and a pointer to pnpm mem:inbox. In particular,
wu:claim does not print a second signal banner: its claim output helper is
silent, preventing a large signal backlog from corrupting stdout. This is an
internal output behavior; command names, options, arguments, and --help
output are unchanged.
Claim and brief cost
Section titled “Claim and brief cost”wu:claim and wu:brief are kept to seconds, because every WU pays them. A claim adds one commit
and one push to origin main; its liveness marker (refs/lumenflow/claims/<WU>) is published on
the first push, not retried; the worktree is listed a small fixed number of times whatever the size
of the board; each WU spec is parsed once per claim; and the three --help probes that prove the
worktree can run wu:brief, wu:edit and wu:prep run at the same time, with every failure
reported. Running wu:brief again with the same client over an unchanged brief records nothing and
makes no commit, as long as the earlier evidence is still comfortably inside the
wu.brief.freshnessMinutes window; an older record, a changed brief or another client records as
usual. The two git hooks that guard these commands each have one narrow fast path (see
Agent safety); enforcement for main, WU YAML and ordinary commits is
unchanged.
WU Types
Section titled “WU Types”wu:create --type accepts seven canonical WU types:
| Type | Use it for |
|---|---|
feature | Net-new user or system capability |
bug | Broken or regressed behavior fix |
documentation | Documentation-only change |
process | Workflow or operating-process maintenance |
tooling | Tooling, automation, or developer-experience work |
chore | Repository upkeep or housekeeping |
refactor | Behavior-preserving code restructure |
Two rules matter in practice:
featureWUs requirespec_refs. A plan link or spec reference is part of the contract, not an optional note.documentationandprocessare the non-code WU types. Use them when the work truly changes docs or operating workflow without touching runtime behavior.
Right-Sizing WUs
Section titled “Right-Sizing WUs”One coherent outcome stays in one WU. Use checkpoint-resume when it will exceed a single session,
and use an orchestrated strategy for complex atomic work. Split only when the pieces can ship,
review, and roll back independently.
Too Big
Section titled “Too Big”❌ “Implement user authentication system”
Right-Sized
Section titled “Right-Sized”✅ “Add login form UI” ✅ “Add password validation” ✅ “Connect login to auth API” ✅ “Add session persistence”
See Sizing Guide for detailed heuristics.
WUs and Pull Requests
Section titled “WUs and Pull Requests”A WU defines the work and its completion evidence. The landing route depends on the claim and completion mode:
| Landing route | Lifecycle |
|---|---|
| Direct merge (worktree or branch-only) | Create WU → Claim → Implement → Run gates and any enforced independent review → wu:prep → wu:done merges and stamps. |
| PR (worktree-PR or branch-PR) | Create WU → Claim → Implement → Run gates and any enforced independent review → wu:prep → wu:done opens a PR. The Git host owns PR review and merge; run wu:cleanup after merge. |
A worktree claim can select PR mode with wu:claim --pr-mode, and a normal worktree can request a PR at completion with wu:done --create-pr. Branch-PR mode includes wu:claim --cloud and wu:claim --branch-only --pr-mode.
Gates verify configured checks. When the worker execution contract enforces independent review, a separate authorized reviewer records a verdict bound to the candidate and contract. Auto-derived requirements are currently observed and reported without refusing completion. Automated gate output cannot supply a reviewer verdict or replace the Git host’s PR and merge policies.
External Work Items
Section titled “External Work Items”Some WUs correspond to external QA or work-item systems: Skynexe Hub, Jira, Linear, GitHub Issues, Azure DevOps, or a project-local review queue. Do not put “run this close-out command” in notes. Notes are context, not an enforcement boundary.
Use external_work_items for structured metadata:
| Field | Meaning |
|---|---|
provider_id | Provider config key, such as hub, jira, linear, github, ado |
item_ids | One or more external item identifiers |
desired_final_status | Status LumenFlow should set after successful local wu:done |
lifecycle_intent | implementation_done, ready_for_review, or custom |
closeout_on | wu_done for automatic lifecycle-owned close-out, or manual |
rollback_guidance | Optional reconciliation guidance shown/audited when close-out fails |
Provider commands and APIs are configured under software_delivery.external_work_items using
pnpm config:set, not by hand-editing workspace.yaml. wu:brief shows the external items as
context, but workers must not run provider mutation commands. Successful local wu:done runs the
configured close-out adapter, verifies the readback adapter, and writes
.lumenflow/telemetry/external-closeout.ndjson.
A command adapter’s command and args are run as an argument list, never through a shell. On
Windows, a bare command name is looked up on PATH the way the command prompt looks it up, so an
adapter that is a .cmd wrapper (npx, or a tool installed with npm install -g) runs through
the command processor with its arguments quoted by LumenFlow. When the adapter cannot start, the
failure names the cause (for example spawn npx ENOENT) in the wu:done output and the audit row.
Review systems can distinguish “ready for review” from final verdict. For example, a Hub provider can set implementation to PBI Done / origin snag ReadyForReview while reserving Pass/Fail for the PO or user.
Evidence, Not Claims
Section titled “Evidence, Not Claims”LumenFlow provides two levels of completion evidence:
Kernel-Level Evidence
Section titled “Kernel-Level Evidence”Every tool call during a WU’s execution is recorded in the evidence store:
- Content-addressed inputs — Tool inputs are hashed with SHA-256 and stored as immutable blobs
- Tool traces —
TOOL_CALL_STARTEDandTOOL_CALL_FINISHEDentries with timing, scope enforcement results, and policy decisions - Denial records — Even denied tool calls produce evidence, proving what the agent attempted
WU-Level Evidence
Section titled “WU-Level Evidence”The completion lifecycle leaves:
- A stamp file created by
wu:donein.lumenflow/state/packs/software-delivery/stamps/WU-XXX.done. - Gate and scoped-test results from
wu:prep, where applicable, recording what was checked. - Git history linking the change to the WU spec.
These records show what was checked and completed; they do not prove the semantic correctness of every acceptance criterion.
This audit trail answers: What was the agent asked to do? Was the action authorized? What actually happened? See Evidence Store for the full model.
Creating WUs
Section titled “Creating WUs”The command prints the generated ID. The created WU goes into
docs/operations/tasks/wu/WU-XXX.yaml with status ready.
Plan-less conversations: If your plan lives in chat or a meeting, keep it lightweight:
use --plan to generate a stub at lumenflow://plans/WU-XXX-plan.md, summarize the plan
there, and reference it in spec_refs. Feature WUs require spec_refs; notes do not replace
the plan link.
Next Steps
Section titled “Next Steps”- Lanes — Organizing work streams with scope boundaries
- Gates — Quality enforcement as kernel policies
- Evidence Store — The immutable audit trail behind every WU
- Kernel Runtime — How WU tasks map to kernel task lifecycle
- Sizing Guide — How to size WUs properly