SIGNAL CONTRACT
Signal Contract — Continuous Reasoning Spine
This document defines the contract governing Chalie’s signal-driven architecture. It is the governing spec for all service integration decisions.
Governing Principles
Simplicity Over Cleverness
Every service must be as minimal as possible. Complexity compounds across 40+ services. When in doubt, do less.
Graceful Isolation
“Forgetting my name for a split-second doesn’t put me in a vegetative state.”
No service failure may cascade into other services. Every signal consumer must operate under the assumption that any signal source may be dead, delayed, or producing garbage. The system degrades gracefully — individual capabilities may temporarily weaken, but the core reasoning loop never stops.
Concrete rules:
- Every signal consumption is wrapped in try/except at the boundary
- Every service has a fail-open default: if it cannot do its job, it returns a neutral result (empty string, no-op, skip), never raises into the caller
- No service holds locks that other services need
- No service writes state that another service must read to function — MemoryStore state is advisory, never mandatory
- “No signal” means “nothing interesting happened”, not “something is broken”
Independent Testability
Every service must be testable in complete isolation. Unit tests use in-memory storage with no shared state and no dependency on another service being initialized. Integration between services is tested by the nightly suite, not by unit tests.
Service Layers
Every service belongs to exactly one of three layers. Failures are contained within a layer — they never cascade across layer boundaries.
| Layer | Analogy | What it does | If it fails… |
|---|---|---|---|
| Cognitive | Brain | Memory formation, consolidation, decay, planning, reflection, reasoning | …you stop reasoning well, but you can still perceive and use tools |
| Embodiment | Body/Senses | Ambient awareness, place learning, context tracking, voice I/O, session events | …you lose situational awareness, but the reasoning loop continues on what it knows |
| Capability | Tools/Hands | External tools, document processing, scheduling, list management | …you lose specific abilities, but you find alternatives or report inability |
Cross-layer rules:
- Cognitive services never import embodiment or capability services at module level — lazy imports only
- Embodiment services write to MemoryStore; cognitive services read from MemoryStore. No direct calls between layers.
- Capability failures surface as “tool unavailable” — the cognitive layer plans around them, never crashes
- A full embodiment outage means ambient signals stop arriving; the cognitive layer treats this as idle, not as an error
Signal Envelope
Signals carry references and summaries, not fat payloads. Every signal includes:
signal_type— what happened (see registered types below)source— which service emitted itconcept_id/concept_name— direct reference to the relevant knowledge nodetopic— domain contextcontent— freeform summary, under 200 charactersactivation_energy— 0.0–1.0, how important or urgenttimestamp— when emitted
Emission is always fire-and-forget. Emitters never wait for a response and never instantiate the consumer. A failed emit is logged at DEBUG level and never raised.
Registered Signal Types
| Signal Type | Meaning | Energy Range |
|---|---|---|
memory_pressure |
Knowledge is fading or contradicted | 0.5–0.7 |
new_knowledge |
New concept formed from experience | 0.6 |
novel_observation |
Surprising tool output stored to data graph | 0.6 |
ambient_context |
Environment changed (place, attention, energy) | From confidence |
idle_discovery |
Nothing happened; engine self-seeds | 0.4–0.5 |
episode_created |
New narrative episode consolidated | 0.5 |
trait_changed |
User trait created, updated, or corrected | 0.3–0.7 |
task_state_changed |
Persistent task state transition | 0.5–0.6 |
schedule_fired |
Scheduled reminder or task fired | 0.5 |
thread_expired |
Conversation thread expired | 0.3 |
user_message |
User sent a chat message | 1.0 |
goal_inferred |
Recurring topic pattern detected as potential goal | 0.6 |
Adding a new signal type requires: an entry in this table, a nightly test scenario, and documentation of what the consumer should do with it.
Signal Transport
- Priority queue — user messages are processed first
- Background queue — all other signal types
- Max depth — 50 signals; oldest dropped on overflow (background queue only)
- Debounce — 30s minimum between processed background signals; user messages bypass
- Yield points — background signal processing checks the priority queue before expensive operations; if a user message is waiting, background reasoning aborts
Service Health
Services write periodic heartbeats keyed by service name with a TTL of 2x their expected cycle time. The self-model aggregates these and includes dead services in its noteworthy list. Health is observational, not coercive — no automated restart.
Migration State
Migration from timer-based to signal-driven architecture is partial. The reasoning loop is fully signal-driven. Most other services still run on timers and emit signals on relevant events. Services that run on long natural cycles (6h, 30min) are good candidates to remain timer-driven indefinitely.
Anti-Patterns
Signal cascades — Service A emits a signal, B processes it and emits, C processes it and emits back toward A. No circular signal paths. Draw the signal graph before adding a new emission point.
Signal as RPC — emitting a signal and waiting for a response. Signals are fire-and-forget. If a response is needed, use a direct function call or a dedicated result queue.
Mandatory signals — a service that crashes or errors if it does not receive a signal within N seconds. Timeouts trigger idle or maintenance behavior, not error states.
Fat signals — payloads containing full episode text, embeddings, or large data structures. Signals carry references and short summaries; consumers look up full data from storage.
Signal-driven configuration — using signals to propagate config changes. Config is read from files or the database at service init or on a slow reload cycle. Signals carry cognitive events, not infrastructure state.
The Spine
Current design: single consumer, single queue. Multi-consumer routing — where each service registers interest in specific signal types — is deferred until enough services are signal-driven that routing becomes necessary. Build it when you need it.