Skip to content

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.

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.

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.

NEEDLE_FIELDS
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.

Two states: open and closed. No draft, no in-review, no backlog. The transition is one-way.

CREATE (ostk work add)

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.

CLOSE (ostk work close)

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
$ 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
$ ostk work close →064 --reason "implemented in wave G.4"
AC: PASS
closed →064

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.

STORAGE

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.

PURPOSE

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
$ ostk hay "nudge delivery should retry on dead targets"
hay filed

$ ostk hay "tui overlay needs scrollback buffer"
hay filed

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.

01
INTELLIGENCE_CLUSTERING
Reads all hay.filed events from the audit log. Clusters hay entries by shared concept terms (a fixed vocabulary of ~80 terms like "kernel", "tui", "agent", "compile"). Uses union-find to group entries that share at least one concept term. When embeddings are available, uses cosine-similarity leader clustering instead (threshold 0.75). Each cluster gets a theme label derived from the most frequent concept term. Clusters are cross-referenced with existing needle spheres via Jaccard similarity on concept overlap.
02
JOINT_GRAPH_REBUILD
Discovers dependency edges from needle titles using regex patterns ("depends on →NNN", "blocks →NNN", "requires →NNN", etc.). Also discovers implicit connections: shared-reference edges (two needles both mention →NNN) and concept edges (two needles share a concept term). Persists newly discovered edges onto needle records as depends_on[] and blocks[] arrays. Rebuilds the full adjacency graph and computes spheres (connected components via DFS).
03
TACK_LINT
Validates each hay entry against the tack grammar. Entries that parse as valid tack commands are flagged as needle candidates. Entries with unknown verbs stay as hay. This pass bridges raw intent (hay) and structured work (needles) without requiring manual triage.
ostk work compile
$ 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.

POINT

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.

RADIUS

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.

SORTING

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.

JOINTS

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.

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.

GRAPH

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.

SCORING

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
$ 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)

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.

Are dependencies enforced?
No. Dependencies are advisory. Closing →A before →B is allowed. The kernel does not block or warn on out-of-order closure. Dependencies exist to inform compound scoring and sphere structure, not to gate work.
How are edges discovered?
Two paths: explicit (ostk work link →A blocks →B) and implicit (ostk work compile discovers patterns in titles like "depends on →NNN" and persists them as structured depends_on[]/blocks[] arrays).
What about ID normalization?
Needle IDs accept multiple formats: →576, ->576, 576, nd-576, bd-576. All are normalized to canonical →NNN form (zero-padded to 3 digits minimum). The normalization handles both bare numeric and prefixed inputs.

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.

P0
compound weight: 3×

Sorts first in listings. Weights 3× in compound scoring. Use for work that blocks everything else.

P1
compound weight: 2×

Sorts second. Weights 2× in compound scoring. Standard priority for planned work.

P2
compound weight: 1×

Sorts third. Weights 1× in compound scoring. Lower urgency, still tracked.

P3
compound weight: 1×

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.