Needles
A needle is the atomic unit of work. It has a title, priority, acceptance criteria, and a lifecycle that moves from open to closed. Everything else in the work system — hay, compile, spheres, compounds — exists to create, organize, and prioritize needles.
01 // WHAT_A_NEEDLE_IS
A needle is an atomic work item with acceptance criteria. It is the unit of planning, assignment, and completion in ostk. Every needle has a unique ID (format: →NNN), a priority tier, and a status. Needles are created explicitly via ostk work add or compiled from hay via ostk work compile.
02 // STORAGE
Needles are stored as JSONL (one JSON object per line) in .ostk/needles/issues.jsonl. A monotonic counter at .ostk/needles/counter is maintained as a write-through cache, but the canonical source of truth for the next ID is max(id) from the JSONL file. Atomicity is enforced by flock(2) on .ostk/needles/issues.lock.
id Unique identifier. Format: →NNN (e.g. →042). Allocated atomically under the issues lock. title Human-readable summary. Required in strict mode. Also used for dependency pattern discovery. status Current state: "open" or "closed". No intermediate states in storage. priority P0, P1, P2, or P3. Controls sort order in listings and weight in compound scoring. ac Acceptance criteria. A shell command run on close — exit 0 means pass, non-zero means fail. description Longer explanation. Required in strict mode (CLI and MCP entry points). tags Array of string labels (e.g. ["kernel", "ux"]). Parsed from comma-separated input. depends_on Array of needle IDs this needle depends on. Bidirectional — see Dependencies. blocks Array of needle IDs this needle blocks. Bidirectional — see Dependencies. created_at ISO 8601 timestamp. Set on creation. closed_at ISO 8601 timestamp. Set when status transitions to closed. close_reason Optional reason string provided at close time. commit_ref Git HEAD hash captured at close. Links the closure to a specific commit. 03 // LIFECYCLE
Two states: open and closed. No draft, no in-review, no backlog. The transition is one-way.
In strict mode (all CLI and MCP entry points), four fields are required: title, priority, description, and acceptance criteria. Priority must be one of P0/P1/P2/P3.
ID allocation and row append happen in a single flock acquisition. The previous two-lock pattern was structurally racy — two writers could allocate the same ID.
If the needle has acceptance criteria, the AC command is executed as a shell command. Exit 0 records ac_result: "pass"; non-zero records "fail". The needle is closed regardless of AC outcome.
On close, commit_ref is set to the current git HEAD, closed_at is timestamped, and an optional close_reason is recorded.
$ ostk work add \ --title "Add retry logic to nudge delivery" \ --priority P1 \ --description "Nudges to dead targets are currently lost. Add a durable retry queue." \ --ac "cargo test --test nudge_retry" added →064: Add retry logic to nudge delivery [P1]
$ ostk work close →064 --reason "implemented in wave G.4" AC: PASS closed →064
04 // HAY
Hay is raw intent capture — a thought, idea, or observation that isn't yet structured enough to be a needle. Filing hay is frictionless: one command, one string, no required fields.
Hay does not have its own file. ostk hay "thought" appends a hay.filed event to .ostk/journal.jsonl with the straw (the text), source, and timestamp. The audit log is the canonical store.
Hay captures intent before you know the shape of the work. It accumulates until ostk work compile triages it — clustering related hay, discovering themes, and identifying which hay entries are needle candidates.
$ ostk hay "nudge delivery should retry on dead targets" hay filed $ ostk hay "tui overlay needs scrollback buffer" hay filed
05 // COMPILE
ostk work compile triages hay into needles, discovers dependency edges from title patterns, and rebuilds the sphere model. Three phases, no LLM involved — pure pattern matching.
$ ostk work compile ── intelligence: 3 clusters discovered ── NUDGE (2 hay) → sphere(point=→041) TUI (3 hay) + 1 unclustered ── edges: 12 persisted (4 new, 8 existing) ── ── spheres: 5 connected, 14 isolated ── sphere 1 | 23 needles | point=→041 (d8) | radius=4 point: Cross-host nudge delivery pipeline sphere 2 | 11 needles | point=→098 (d5) | radius=3 point: TUI overlay rendering system
Spheres
A sphere is a connected component of the needle dependency graph. Spheres are computed by DFS over the joint adjacency map — every needle reachable from any other needle in the component belongs to the same sphere.
Each sphere has a point — the hub needle with the highest degree (most connections). Ties are broken by smallest ID for determinism. The point is the center of gravity for the sphere.
BFS from the point computes the radius — the maximum hop distance from the point to any needle in the sphere. Needles closer to the point (lower radius) compound more because they connect to more work.
Spheres are sorted by size, largest first. Isolated needles (single-node spheres) are counted but listed separately. When picking the next needle to work on, needles with lower radius within their sphere sort higher among same-priority needles.
The edges connecting needles within a sphere are called joints. Three types: Explicit (title patterns like "depends on →NNN"), SharedRef (two needles both mention the same →NNN), and Concept (two needles share a concept term). Only open needles participate in the graph.
06 // COMPOUNDS
ostk compounds answers: "What, if completed, unblocks the most other work?" It performs BFS over the blocks[] edges to compute transitive fan-out — how many downstream needles are transitively blocked by each needle.
Built from both structured blocks[] fields and title-parsed signals ("blocks →NNN", "unblocks →NNN", "enables →NNN"). Reverse edges from depends_on[] and title patterns ("depends on →NNN", "requires →NNN", "blocked by →NNN") are also included.
Each needle's weighted score = transitive fan-out × priority weight. Priority weights: P0 = 3×, P1 = 2×, P2/P3 = 1×. Results are sorted by weighted score descending, then transitive fan-out, then ID for stability. The top 10 are displayed.
$ ostk compounds :compounds — highest compounding work →041 Cross-host nudge delivery pipeline [P0] blocks 3 (transitive: 7) →098 TUI overlay rendering system [P1] blocks 2 (transitive: 5) →112 Kernel boot sequence refactor [P1] blocks 1 (transitive: 3)
07 // DEPENDENCIES
Dependencies are bidirectional: adding →A depends_on →B also adds →B blocks →A. The ostk work link command enforces this symmetry — both sides are updated atomically under the issues lock.
08 // PRIORITY_TIERS
Priorities are labels, not automation triggers. There is no auto-dispatch, no SLA enforcement, no escalation policy. Priorities affect two things: sort order and compound weight.
Sorts first in listings. Weights 3× in compound scoring. Use for work that blocks everything else.
Sorts second. Weights 2× in compound scoring. Standard priority for planned work.
Sorts third. Weights 1× in compound scoring. Lower urgency, still tracked.
Sorts last. Weights 1× in compound scoring. Nice-to-have, deferred, or speculative.
Why This Design
Needles are intentionally minimal. Two states, not five. Advisory dependencies, not enforced gates. Pattern-matched clustering, not LLM triage.
The work system is designed for multi-agent coordination where agents come and go (Five Laws #2). An agent that crashes mid-task leaves behind needles in issues.jsonl — a successor agent can read the store, run ostk work compile to rebuild the graph, and pick the highest-compounding open needle without any context about the predecessor. The filesystem is the message bus; the needle store is the backlog; the compound score is the prioritizer. No orchestrator required.
Needles are also the unit of measurement in needle-bench.cc, the open benchmark for context injection across 26+ models — 32 bugs, each a needle with AC.