Signals
A signal is the core coordination record in Chorus — the unit agents emit to talk to each other. One signal carries its content, its routing addressing (from/to role, identity, ring), and the state that tracks both how it was delivered and what work it represents.
The full schema lives in SignalSchema (src/signals/types.ts:48-85). Key fields:
| Field | Meaning |
|---|---|
signal_type |
One of the foundation types below (any non-empty string is accepted by the schema, but emit is gated to 8 — see below). |
content |
Required, non-empty. |
urgency |
0–1, default 0.5. Governs default inbox sort order (sortSignalsByAttention, src/workflow/classification.ts:29-33) and the min_urgency filter (signal.urgency >= min_urgency, e.g. src/db/memory-adapter.ts:441-442). It does not decide now-bucket membership for emittable signals — the legacy ≥ 0.7 → now threshold applies only to task signals (no longer emittable; see below). |
from_role |
Required — a role you fill. See The core loop. |
to_role / to_identity / to_ring |
Optional addressing; omit all three to broadcast. |
delivery_state |
Routing lifecycle (axis 1). Default pending. |
task_status |
Work lifecycle (axis 2). Legacy task signals only. |
tags, resources, metadata, payload |
Optional structured extras. |
The 8 foundation signal types
Section titled “The 8 foundation signal types”CoreSignalTypes (src/signals/types.ts:16-27) exports exactly the 8 foundation
types, and the emit catalog registers one command per type
(FOUNDATION_SIGNAL_TYPES, src/commands/handlers/signals.ts:8-17):
| Type | Use |
|---|---|
pulse |
Heartbeat / “I’m here, here’s status”. |
sense |
An observation about the environment. |
query |
A question that wants a response. |
alert |
Something needs attention. |
artifact |
Announces a produced artifact. |
approval_request |
Requests an approval gate decision. |
proposal |
Proposes a course of action. |
shift |
Handoff / state transition (used by workflow.create_handoff). |
Three independent state axes
Section titled “Three independent state axes”A signal carries up to three orthogonal pieces of state. Do not conflate them — they answer different questions (gap-2 §1.1, §4).
1. delivery_state — routing lifecycle
Section titled “1. delivery_state — routing lifecycle”How the signal moved through the bus. Enum (src/signals/delivery.ts:14-24): pending,
leased, acked, failed, dead_letter. Transitions (src/signals/delivery.ts:50-56):
| From | Allowed → |
|---|---|
pending |
leased, acked |
leased |
leased (extend), pending (release), acked, failed, dead_letter |
failed |
failed, dead_letter |
acked |
acked (terminal) |
dead_letter |
dead_letter (terminal) |
- Inbox-visible states are
{pending, leased}; terminal states are{acked, failed, dead_letter}(src/signals/delivery.ts:58-59). - Self-loops are intentional no-ops:
leased→leasedmodels a successful claim extension (src/signals/delivery.ts:47-56). ackworks on any signal type.claim/extendClaim/cancelonly apply to task signals (see below).
2. task_status — work lifecycle
Section titled “2. task_status — work lifecycle”Tracks the progress of a unit of work. Enum (src/signals/types.ts:35-42): open,
claimed, in_progress, blocked, done, cancelled.
3. receipt_state — per-identity inbox overlay
Section titled “3. receipt_state — per-identity inbox overlay”The third axis is not a field on the signal at all — it is a per-identity overlay
(src/inbox/types.ts:4-15). Stored states: acknowledged, resolved, snoozed; the
effective state adds unread. Because it is per-identity, resolving an item hides it from
your inbox without changing the shared signal or any task state (gap-2 §1.2). Inbox
reads and receipts are covered on their own page.