NeedleEvent Schema
schema-version: 1 — shared contract between NEEDLE and FABRIC.
Both projects must agree on this version. NEEDLE emits schema_version in its
output; FABRIC asserts compatibility during parse via NEEDLE_EVENT_SCHEMA_VERSION
in src/types.ts. If the values diverge, parseNeedleEvent throws.
Wire Format
Every event emitted by NEEDLE — over JSONL and OTLP logs — conforms to this
shape:
{
"schema_version": 1,
"timestamp": "2026-04-21T11:20:19.962811515Z",
"event_type": "worker.started",
"worker_id": "tcb-alpha",
"session_id": "d7261357",
"sequence": 1,
"bead_id": "bd-abc123",
"data": {}
}
Fields
| Field |
Type |
Required |
Description |
schema_version |
number |
recommended |
Protocol version. Present in newer NEEDLE output; FABRIC asserts it when present. |
timestamp |
string |
yes |
RFC3339 timestamp. Display only — not authoritative for ordering. |
event_type |
string |
yes |
Taxonomy string from the event taxonomy table below. |
worker_id |
string |
yes |
Worker identifier (e.g. "tcb-alpha"). |
session_id |
string |
yes |
Groups a worker's lifetime events into a single session. |
sequence |
number |
yes |
Per-worker monotonic counter. Authoritative for ordering within a worker. |
bead_id |
string |
no |
Present when the event pertains to a specific bead. |
data |
object |
yes |
Event-specific payload (see taxonomy table for notable fields). May be empty {}. |
Ordering Contract
Sort events by (worker_id, sequence), not by timestamp.
Wall clocks skew across hosts. sequence is the worker's own monotonic counter
and is the only reliable basis for replay and timeline reconstruction within a
single worker. To interleave events from multiple workers, merge-sort on
sequence within each worker_id partition, then order across partitions by
timestamp as a tiebreaker.
Event Taxonomy
Format: category.action. Categories group related lifecycle phases.
Worker Lifecycle
event_type |
Description |
Notable data fields |
worker.started |
Worker boot |
version, worker_name |
worker.idle |
Worker is idle, waiting for work |
— |
worker.stopped |
Worker exit |
reason |
worker.draining |
Worker is draining before shutdown |
— |
Bead Lifecycle
event_type |
Description |
Notable data fields |
bead.claimed |
Bead claim succeeded |
bead_id |
bead.prompt_built |
Prompt constructed for agent |
bead_id |
bead.agent_started |
Agent began working on bead |
bead_id |
bead.agent_completed |
Agent finished working on bead |
bead_id, duration_ms |
bead.completed |
Bead work fully completed |
bead_id, duration_ms |
bead.failed |
Bead work failed |
bead_id, error |
bead.released |
Bead released back to queue |
bead_id |
bead.claim_retry |
Claim attempt will be retried |
bead_id, attempt |
bead.claim_exhausted |
All claim retries exhausted |
bead_id |
Bead Mitosis
event_type |
Description |
Notable data fields |
bead.mitosis.check |
Checking if bead should split |
bead_id |
bead.mitosis.started |
Mitosis began |
bead_id |
bead.mitosis.child_created |
Child bead created |
bead_id, child_id |
bead.mitosis.complete |
Mitosis finished |
bead_id |
bead.mitosis.failed |
Mitosis failed |
bead_id, error |
bead.mitosis.skipped |
Mitosis skipped (not needed) |
bead_id |
Strand Lifecycle
event_type |
Description |
Notable data fields |
strand.started |
Strand execution began |
strand |
strand.completed |
Strand execution finished |
strand, duration_ms |
strand.fallthrough |
Strand found no work |
strand |
strand.skipped |
Strand was skipped |
strand, reason |
Hook Lifecycle
event_type |
Description |
Notable data fields |
hook.started |
Hook execution began |
hook |
hook.completed |
Hook execution finished |
hook, duration_ms |
hook.failed |
Hook execution failed |
hook, error |
Heartbeat
event_type |
Description |
Notable data fields |
heartbeat.emitted |
Periodic heartbeat |
status |
heartbeat.stuck_detected |
Worker appears stuck |
worker_id, since |
heartbeat.recovery |
Worker recovered from stuck state |
worker_id |
Mend (Maintenance)
event_type |
Description |
Notable data fields |
mend.orphan_released |
Orphaned bead released |
bead_id |
mend.heartbeat_cleaned |
Stale heartbeat cleaned |
worker_id |
mend.logs_pruned |
Old logs pruned |
bytes_freed |
mend.completed |
Mend cycle finished |
duration_ms |
Unravel (Alternatives)
event_type |
Description |
Notable data fields |
unravel.alternatives_created |
Alternative beads created |
parent_bead_id, count |
unravel.alternative_created |
Single alternative bead created |
parent_bead_id, child_bead_id |
unravel.analysis_started |
Alternatives analysis began |
bead_id |
unravel.analysis_completed |
Alternatives analysis finished |
bead_id, duration_ms |
Weave (Documentation Gaps)
event_type |
Description |
Notable data fields |
weave.bead_created |
Documentation gap bead created |
bead_id, file, line |
weave.analysis_started |
Documentation analysis began |
— |
weave.analysis_completed |
Documentation analysis finished |
gaps_found, duration_ms |
Pulse (Health Monitoring)
event_type |
Description |
Notable data fields |
pulse.bead_created |
Health issue bead created |
bead_id, detector |
pulse.scan_started |
Health scan began |
— |
pulse.scan_completed |
Health scan finished |
issues_found, duration_ms |
pulse.issue_detected |
Specific issue found |
detector, severity |
pulse.detector_started |
Individual detector started |
detector |
pulse.detector_completed |
Individual detector finished |
detector, duration_ms |
Error Events
event_type |
Description |
Notable data fields |
error.claim_failed |
Bead claim failed |
bead_id, error |
error.agent_crash |
Agent process crashed |
bead_id, error, exit_code |
error.timeout |
Operation timed out |
bead_id, duration_ms |
error.release_failed |
Bead release failed |
bead_id, error |
Effort & Budget
event_type |
Description |
Notable data fields |
effort.recorded |
Effort measurement recorded |
bead_id, tokens, cost |
budget.warning |
Budget approaching limit |
used, limit, percentage |
budget.exceeded |
Budget exceeded |
used, limit |
budget.per_bead_exceeded |
Per-bead budget exceeded |
bead_id, used, limit |
File Locks
event_type |
Description |
Notable data fields |
file.checkout |
File checked out for editing |
path |
file.conflict |
File conflict detected |
path, workers |
file.release |
File lock released |
path |
file.stale |
Stale file lock detected |
path |
lock.priority_bump |
Lock priority bumped |
path, worker_id |
lock.priority_bump_received |
Received priority bump notification |
path, from_worker |
lock.expired |
Lock expired |
path |
OTLP Metric Instruments
When NEEDLE workers emit OTLP Metric payloads (Sum, Histogram, Gauge), FABRIC
normalizes each data point into a metric.{name} event (see normalizer). The
canonical instrument names below define the mapping to analytics DB columns in
fabric.db.
FABRIC's Analytics Writer prefers OTLP metric values over log-derived
estimates when both are present for the same worker + session.
Token & Cost Instruments
| Instrument Name |
Type |
Unit |
DB Column(s) |
needle.worker.tokens.in |
Sum |
tokens |
task_metrics.tokens_in, session_worker_summaries.tokens_in |
needle.worker.tokens.out |
Sum |
tokens |
task_metrics.tokens_out, session_worker_summaries.tokens_out |
needle.worker.cost.usd |
Sum |
USD |
task_metrics.cost, session_worker_summaries.cost_usd |
Duration Instruments
| Instrument Name |
Type |
Unit |
DB Column(s) |
needle.bead.duration |
Histogram |
ms |
task_metrics.duration_ms |
needle.worker.uptime |
Gauge |
ms |
— (informational) |
Counting Instruments
| Instrument Name |
Type |
Unit |
DB Column(s) |
needle.bead.completed |
Sum |
count |
session_worker_summaries.beads_completed |
needle.bead.failed |
Sum |
count |
session_worker_summaries.beads_failed |
needle.worker.errors |
Sum |
count |
session_worker_summaries.errors |
Accepted Aliases
NEEDLE's telemetry module (OtlpMetricSink) emits some instruments under
slightly different names. FABRIC resolves these to the canonical names above
via the INSTRUMENT_ALIASES map in src/workerAnalytics.ts:
| NEEDLE Emitted Name |
Canonical Name |
needle.worker.beads.completed |
needle.bead.completed |
needle.worker.beads.failed |
needle.bead.failed |
Attribute Requirements
Every metric data point must carry these OTLP attributes (namespaced form
preferred, plain form accepted as fallback):
| Attribute |
Required |
Purpose |
needle.worker.id / worker_id |
yes |
Worker identity |
needle.session.id / session_id |
yes |
Session grouping |
needle.bead.id / bead_id |
for bead-scoped metrics |
Task correlation |
Resolution Order
When querying fabric.db, FABRIC resolves conflicting values in this order:
otlp-metric — row sourced from an OTLP metric instrument (authoritative)
otlp-span — duration derived from span start/end times
log-derived — estimated from log message parsing (fallback)
The metrics_source column on sessions and session_worker_summaries records
which source was used.
TypeScript Reference
The canonical TypeScript definitions live in src/types.ts:
NeedleEvent — the wire-schema interface
NeedleEventType — union of all known event_type strings
NEEDLE_EVENT_SCHEMA_VERSION — the current protocol version constant
The parser in src/parser.ts validates incoming events against the schema and
throws on version mismatch. Legacy LogEvent is retained as an adapter for
backward compatibility with existing UI consumers.