Skip to content

Work Units (WUs)

A Work Unit (WU) is a small, well-defined piece of work with clear acceptance criteria and proof of completion.

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)
id: <wu-id>
title: Add email validation to login form
lane: 'Experience: UI'
type: feature
status: ready
priority: P2
exposure: ui

description: |
  Add client-side email validation before form submission.
  Show inline error message for invalid formats.

acceptance:
  - Email field validates on blur
  - Invalid emails show "Please enter a valid email"
  - Valid emails allow form submission
  - Unit test covers validation logic

test_paths:
  unit:
    - src/components/__tests__/login-form.test.ts
  e2e:
    - e2e/login.spec.ts

code_paths:
  - src/components/LoginForm.tsx
  - src/utils/validation.ts

external_work_items:
  - provider_id: hub
    item_ids:
      - PBI-123
      - SNAG-456
    desired_final_status: ReadyForReview
    lifecycle_intent: ready_for_review
    closeout_on: wu_done

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.

A canonical id is <prefix><zero-padded-integer> where:

  • prefix matches /^[A-Z][A-Z0-9]{1,9}-$/ (default 'WU-').
  • the integer body is at least width characters wide (default 4).
  • the integer body may be wider than width once the sequence overflows (a five-digit body is canonical even when width=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.

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.

Canonical ID behaviour is controlled by software_delivery.wu_id.* in workspace.yaml:

software_delivery:
  wu_id:
    width: 4 # 1-6; default 4 (zero-padded to four digits)
    strict: true # enforce canonical format at creation; lumenflow:doctor --deep flags drift
    prefix: 'WU-' # pattern /^[A-Z][A-Z0-9]{1,9}-$/; try TASK-, STORY-, TICKET-, …
# Fresh repo, no config changes: auto-generated ids are zero-padded, for example <wu-id>
pnpm wu:create --lane "Experience: UI" --title "Add login form" …
# ↳ <wu-id>

# --id input is canonicalized before any file is written:
pnpm wu:create --id <wu-id> --title "…"
# ↳ writes <wu-id>.yaml with id: <wu-id>

Teams that prefer a non-WU- prefix can pick a different one without touching any WU source file:

pnpm config:set --key software_delivery.wu_id.prefix --value TASK-
pnpm wu:create --lane "Experience: UI" --title "Add login form" …
# ↳ TASK-0001

pnpm wu:create --id TASK-14 --title "…"
# ↳ writes TASK-0014.yaml with id: TASK-0014

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:

pnpm wu:rename --from <wu-id> --to <wu-id>

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:

pnpm wu:rename --from <wu-id> --to <wu-id> --replay-only

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: true (default) — lumenflow:doctor --deep treats 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:

RowWhere it livesTracked
claim, complete, release, block and the other lifecycle events, plus the checkpoint proof rows and compact stubs described belowwu-events.jsonlYes
checkpoint free text: the note, progress and next steps of mem:checkpoint, phase checkpoints and wu:edit --notes bodiescheckpoint-journal.jsonl (shared by every worktree of a clone)No
gate_attempt, lifecycle_durationtelemetry-journal.jsonl (shared by every worktree of a clone)No
heartbeatheartbeat-journal.jsonlNo
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:

pnpm state:cleanup --compact-ledger --dry-run   # preview
pnpm state:cleanup --compact-ledger             # moves the rows byte for byte, keeps them in the journals

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.

pnpm state:cleanup --events-only --dry-run

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.

ready → in_progress → [blocked] → done
StatusMeaning
readyApproved, can be claimed
in_progressSomeone is working on it
blockedWaiting on external dependency
doneLifecycle completed; stamp created

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.

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:create --type accepts seven canonical WU types:

TypeUse it for
featureNet-new user or system capability
bugBroken or regressed behavior fix
documentationDocumentation-only change
processWorkflow or operating-process maintenance
toolingTooling, automation, or developer-experience work
choreRepository upkeep or housekeeping
refactorBehavior-preserving code restructure

Two rules matter in practice:

  • feature WUs require spec_refs. A plan link or spec reference is part of the contract, not an optional note.
  • documentation and process are the non-code WU types. Use them when the work truly changes docs or operating workflow without touching runtime behavior.

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.

❌ “Implement user authentication system”

✅ “Add login form UI” ✅ “Add password validation” ✅ “Connect login to auth API” ✅ “Add session persistence”

See Sizing Guide for detailed heuristics.

A WU defines the work and its completion evidence. The landing route depends on the claim and completion mode:

Landing routeLifecycle
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.

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:

FieldMeaning
provider_idProvider config key, such as hub, jira, linear, github, ado
item_idsOne or more external item identifiers
desired_final_statusStatus LumenFlow should set after successful local wu:done
lifecycle_intentimplementation_done, ready_for_review, or custom
closeout_onwu_done for automatic lifecycle-owned close-out, or manual
rollback_guidanceOptional 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.

LumenFlow provides two levels of completion 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_STARTED and TOOL_CALL_FINISHED entries with timing, scope enforcement results, and policy decisions
  • Denial records — Even denied tool calls produce evidence, proving what the agent attempted

The completion lifecycle leaves:

  1. A stamp file created by wu:done in .lumenflow/state/packs/software-delivery/stamps/WU-XXX.done.
  2. Gate and scoped-test results from wu:prep, where applicable, recording what was checked.
  3. 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.

# With required flags
pnpm wu:create \
  --title "Add password strength meter" \
  --lane "Experience: UI" \
  --type feature \
  --exposure ui \
  --description "Show password strength indicator during registration" \
  --acceptance "Strength meter shows weak/medium/strong" \
  --acceptance "Meter updates in real-time as user types" \
  --notes "Reuse the existing password policy and form tokens" \
  --code-paths "src/components/PasswordStrength.tsx" \
  --test-paths-unit "src/components/__tests__/password-strength.test.ts" \
  --test-paths-manual "Navigate to Registration with a keyboard and verify the labelled meter updates without disrupting focus" \
  --plan

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.

  • 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