INTERFACES
Interfaces — Communication Contract
This document defines how external systems connect to Chalie, what protocols they use, and how data flows in and out. It is the governing specification for all interface integration work.
Core insight: Chalie is a cognitive router, not a message router. It does not forward data between interfaces. It receives signals and messages from the world, interprets them through the lens of its human, and decides what matters. Interfaces are deaf to each other — they only interact with Chalie.
1. The Two Channels
Every piece of data flowing into Chalie is one of two things:
| Signal | Message | |
|---|---|---|
| Analogy | Overhearing a conversation | Someone talking directly to you |
| Cost | Zero LLM tokens | LLM reasoning cost |
| Destination | World state (deterministic, in-memory) | Reasoning loop (cognitive pipeline) |
| Urgency | Passive — Chalie notices when relevant | Active — Chalie reasons about it now |
| Example | “AAPL up 10%” | “Your appointment moved to 3pm” |
| Broadcast? | Yes — one source, many Chalies | No — targeted to one Chalie |
Signals update Chalie’s world state. They decay naturally (6-hour half-life). The reasoning loop sees them during idle cycles and surfaces them if they intersect with something the human cares about. A restaurant broadcasting “closed tonight” becomes relevant only if the human was planning to go there.
Messages enter the cognitive pipeline. Chalie reasons about them, may act on them, and responds. A hospital sending “your appointment moved” triggers reasoning, which may reschedule things, notify the user, or invoke tools.
2. Communication Topology
┌─────────────────────────────────────────┐
│ CHALIE INSTANCE │
│ │
INBOUND │ ┌───────────┐ ┌────────────────┐ │ OUTBOUND
──────────────────┼──►│ World │ │ Reasoning │ │ ──────────────────
│ │ State │ │ Loop │ │
Signals (passive) │ │ │ │ │ │ Tool execution
POST /api/signals─┼──►│ zero LLM │ │ LLM pipeline │──┼──► POST interface/execute
│ │ │───►│ (idle cycle) │ │
WS subscription ──┼──►│ │ │ │ │ Client push
│ └───────────┘ │ │──┼──► WebSocket /ws
│ │ │ │
Messages (direct) │ │ │ │
POST /api/messages┼───────────────────►│ │ │
WebSocket /ws ────┼───────────────────►│ │ │
│ └────────────────┘ │
└─────────────────────────────────────────┘
2.1 Inbound (Data Flowing INTO Chalie)
| Transport | Endpoint | Channel | Use Case |
|---|---|---|---|
| HTTP POST | /api/signals |
Signal → world state | Point-to-point signal from interface |
| HTTP POST | /api/signals/batch |
Signal → world state | Batch signals (up to 50 per request) |
| HTTP POST | /api/messages |
Message → reasoning loop | Direct communication from interface |
| WebSocket | Chalie subscribes outbound to interface stream | Signal → world state | Broadcast streams (stock feeds, IoT, news) |
| WebSocket | /ws (chat type) |
Message → reasoning loop | Human chat messages |
2.2 Outbound (Data Flowing OUT of Chalie)
| Transport | Target | Purpose |
|---|---|---|
WebSocket /ws |
Human clients (chat UI, mobile) | Push responses, cards, notifications |
| HTTP POST | Interface /execute endpoint |
Invoke interface tools |
2.3 Interfaces Are Deaf to Each Other
The stock exchange never sees medical data. The hospital never sees stock data. Neither connects to /ws. They only receive data from Chalie when Chalie explicitly invokes one of their tools — scoped, intentional, and gated by the reasoning loop.
3. Signal Contract
3.1 Point-to-Point Signal — POST /api/signals
Auth: Bearer token (issued during pairing) or session cookie.
Request schema:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
signal_type |
string | yes | — | Signal category (e.g. price_alert, weather_update, emergency) |
content |
string | yes | — | Human-readable signal description |
source |
string | no | wrapper_id | Originating system identifier |
topic |
string | null | no | null | Topic hint for salience scoring |
activation_energy |
float 0–1 | no | 0.5 | Salience weight — higher values persist longer in world state |
metadata |
object | null | no | null | Arbitrary key-value context |
Response: 202 {"ok": true, "signal_id": "<uuid>"}
Error responses:
| Code | Condition |
|---|---|
| 400 | Validation failure (missing fields, invalid types) |
| 401 | No valid session or bearer token |
| 403 | Wrapper not permitted to emit this signal_type |
| 429 | Rate limit exceeded (100 signals/min per wrapper) |
Example — weather update:
{
"signal_type": "weather_forecast",
"content": "Heavy rain expected this evening, 80% chance",
"source": "weather-service",
"topic": "weather",
"activation_energy": 0.4,
"metadata": {
"precipitation_chance": 0.8,
"temperature_high": 18,
"temperature_low": 12
}
}
Example — stock price alert:
{
"signal_type": "price_alert",
"content": "AAPL at $185.50, up 10.2% today",
"source": "stock-exchange",
"activation_energy": 0.7,
"metadata": {
"ticker": "AAPL",
"price": 185.50,
"change_pct": 10.2,
"volume": 89420000
}
}
3.2 Batch Signals — POST /api/signals/batch
Auth: Bearer token or session cookie.
Request body: JSON array of signal objects (same schema as single signal). Maximum 50 per request.
Response: 200 {"accepted": N, "rejected": M, "errors": [{"index": I, "error": "..."}]}
Each signal is validated, capability-checked, and rate-limited independently. Valid signals are stored even when others in the batch fail.
3.3 Broadcast Signals (WebSocket Subscription)
For high-volume broadcast streams (stock tickers, IoT sensors, news feeds), Chalie subscribes outbound to the interface’s stream rather than receiving individual HTTP calls.
Stream message schema (same fields as POST /api/signals, wrapped in a type envelope):
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | yes | Must be "signal" |
signal_type |
string | yes | Signal category |
content |
string | yes | Human-readable description |
source |
string | no | Originating system identifier |
topic |
string | null | no | Topic hint |
activation_energy |
float 0–1 | no | Salience weight (default 0.5) |
metadata |
object | null | no | Arbitrary context |
Example — broadcast stream event:
{
"type": "signal",
"signal_type": "price_update",
"content": "MSFT at $420.10, down 0.5%",
"source": "nasdaq",
"activation_energy": 0.3,
"metadata": {"ticker": "MSFT", "price": 420.10}
}
The interface broadcasts to all subscribers. It does not know how many Chalies are listening. Each Chalie independently updates its world state.
Status: The subscription worker is planned infrastructure. Point-to-point signals via REST are implemented. Broadcast subscription will be added when the first broadcast interface is built.
3.4 Signal Lifecycle
- Signal arrives (REST or stream subscription)
- Written to an in-memory capped list (max 100 entries, oldest dropped)
- Scored on each world state assembly:
- Temporal decay: 6-hour half-life (24h old ≈ 6% salience)
- Activation energy amplifier: higher energy = stays visible longer
- Threshold: 0.15 minimum salience to appear
- Competes with other world state items (scheduled reminders, tasks, ambient context) — only top 5 most salient items enter the reasoning context
- Reasoning loop idle cycle discovers relevant signals and may surface them proactively
- Signals that decay below threshold naturally disappear — no cleanup needed
4. Message Contract
4.1 Human Messages — WebSocket /ws
Auth: Session cookie validated at WebSocket upgrade.
Request schema (client → server):
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
type |
string | yes | — | Must be "chat" |
text |
string | yes | — | Message text (can be empty if image_ids present) |
source |
string | no | "text" |
Input method: "text" or "voice" |
image_ids |
string[] | no | — | Pre-uploaded image IDs (max 3) |
4.2 Interface Messages — POST /api/messages
Auth: Bearer token (issued during pairing).
Status: This endpoint is planned. The schema below defines the contract.
Request schema:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
text |
string | yes | — | Message content directed at Chalie |
source |
string | no | wrapper_id | Originating interface identifier |
topic |
string | null | no | null | Topic hint |
metadata |
object | null | no | null | Structured context (appointment IDs, timestamps, etc.) |
Response: 202 {"ok": true, "message_id": "<uuid>"}
Example — appointment change:
{
"text": "Your appointment has been moved from 2:00 PM to 3:00 PM tomorrow",
"source": "hospital-portal",
"topic": "health",
"metadata": {
"appointment_id": "apt_12345",
"original_time": "2026-03-18T14:00:00Z",
"new_time": "2026-03-18T15:00:00Z",
"clinic": "Cardiology",
"doctor": "Dr. Smith"
}
}
Example — urgent server alert:
{
"text": "Production server api-03 is unresponsive. Last healthy check was 5 minutes ago.",
"source": "monitoring-system",
"topic": "infrastructure",
"metadata": {
"server": "api-03",
"last_healthy": "2026-03-17T14:25:00Z",
"severity": "critical"
}
}
4.3 When to Use Signals vs Messages
| Scenario | Channel | Why |
|---|---|---|
| Stock price changed | Signal | Passive information — relevant only if human cares |
| Weather forecast updated | Signal | Background knowledge |
| Hospital emergency broadcast | Signal | Broadcast — each Chalie interprets independently |
| Your appointment moved | Message | Directed at this human — requires reasoning |
| Lab results ready for pickup | Message | Actionable — human needs to know |
| New email received (summary) | Signal | Passive awareness |
| Calendar conflict detected | Message | Requires resolution |
| Restaurant closed tonight | Signal | Only relevant if human planned to go |
| Urgent: server is down | Message | Requires immediate attention |
5. WebSocket Outbound Contract (/ws)
This is how Chalie pushes data to human clients (chat UI, mobile app). No interface connects here.
Auth: Session cookie validated at WebSocket upgrade.
5.1 Client → Server Messages
chat — User message:
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | yes | "chat" |
text |
string | yes | Message text |
source |
string | no | "text" or "voice" |
image_ids |
string[] | no | Pre-uploaded image IDs (max 3) |
action — Deterministic skill invocation:
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | yes | "action" |
payload |
object | yes | Skill name + parameters |
act_steer — Mid-execution redirect:
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | yes | "act_steer" |
text |
string | yes | Redirect instruction |
resume — Reconnect with missed event replay:
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | yes | "resume" |
last_seq |
integer | yes | Last sequence number received |
pong — Keepalive response:
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | yes | "pong" |
5.2 Server → Client Messages
All server messages include a seq (monotonic sequence number) for deduplication on reconnect.
status — Processing stage:
| Field | Type | Description |
|---|---|---|
type |
string | "status" |
stage |
string | Processing stage (e.g. "processing", "thinking", "retrieving_memory", "responding") |
seq |
integer | Sequence number |
message — Final response:
| Field | Type | Description |
|---|---|---|
type |
string | "message" |
blocks |
array | Block array (universal block protocol — no raw text/HTML over the wire) |
topic |
string | Conversation topic |
mode |
string | Cognitive mode (e.g. "RESPOND", "ACT") |
confidence |
float | Response confidence (0–1) |
exchange_id |
string | Unique exchange identifier |
seq |
integer | Sequence number |
act_narration — Live ACT loop progress:
| Field | Type | Description |
|---|---|---|
type |
string | "act_narration" |
text |
string | Narration line (tool invocation or reasoning step) |
step |
integer | Iteration count |
seq |
integer | Sequence number |
card — Tool result card:
| Field | Type | Description |
|---|---|---|
type |
string | "card" |
html |
string | Sanitized HTML content |
css |
string | Scoped CSS styles |
scope_id |
string | CSS deduplication key |
tool_name |
string | Originating tool |
title |
string | null | Card title |
accent_color |
string | null | Hex color for card accent |
output_id |
string | Unique card identifier |
topic |
string | null | Related topic |
seq |
integer | Sequence number |
done — Response complete:
| Field | Type | Description |
|---|---|---|
type |
string | "done" |
duration_ms |
integer | Wall-clock processing time |
seq |
integer | Sequence number |
error — Error:
| Field | Type | Description |
|---|---|---|
type |
string | "error" |
message |
string | Error description |
recoverable |
boolean | Whether the client should retry |
seq |
integer | Sequence number |
notification — Background notification:
| Field | Type | Description |
|---|---|---|
type |
string | "notification" |
content |
string | Notification text |
topic |
string | null | Related topic |
seq |
integer | Sequence number |
escalation — Critic flagged action:
| Field | Type | Description |
|---|---|---|
type |
string | "escalation" |
content |
string | Escalation description |
topic |
string | null | Related topic |
seq |
integer | Sequence number |
task — Persistent task update:
| Field | Type | Description |
|---|---|---|
type |
string | "task" |
seq |
integer | Sequence number |
image_ready — Image analysis complete:
| Field | Type | Description |
|---|---|---|
type |
string | "image_ready" |
image_id |
string | Uploaded image identifier |
status |
string | "success" or "failed" |
seq |
integer | Sequence number |
ping — Keepalive probe (every 15s):
| Field | Type | Description |
|---|---|---|
type |
string | "ping" |
5.3 Reconnection
On reconnect, the client sends {"type": "resume", "last_seq": N}. The server replays all events with seq > N from a 200-event catch-up buffer. Client deduplicates by tracking seen sequence numbers.
6. Tool Execution Contract (Chalie → Interface)
When Chalie’s reasoning loop decides to act, it calls the interface’s execute endpoint.
6.1 Interface Must Expose
GET /health
| Field | Type | Description |
|---|---|---|
status |
string | "ok" |
name |
string | Interface display name |
version |
string | Interface version |
GET /capabilities — Returns array of tool definitions:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Tool identifier (unique within interface) |
description |
string | yes | What the tool does |
documentation |
string | no | Detailed usage documentation |
parameters |
array | yes | Parameter definitions |
parameters[].name |
string | yes | Parameter name |
parameters[].type |
string | yes | Parameter type (string, number, boolean, object) |
parameters[].required |
boolean | yes | Whether the parameter is required |
parameters[].description |
string | yes | Parameter description |
returns |
object | no | Return type description |
POST /execute — Chalie invokes a tool:
Request:
| Field | Type | Required | Description |
|---|---|---|---|
capability |
string | yes | Tool name to invoke |
params |
object | yes | Tool parameters |
Response:
| Field | Type | Description |
|---|---|---|
text |
string | null | Human-readable result |
data |
object | null | Structured result data |
error |
string | null | Error description (null on success) |
blocks |
array | null | Block array for UI rendering (overlay updates) |
openUrl |
string | null | URL to open in a new browser tab |
GET / — Returns the daemon’s UI for the app overlay:
| Field | Type | Description |
|---|---|---|
blocks |
array | Block array describing the interface UI |
Daemons return Block[] from renderInterface() at their root path (/). The gateway proxies /gw/<interface_id>/render → daemon’s /, adds a gateway field, and forwards to the frontend, which renders blocks using BlockRenderer. No HTML, JS, or CSS from daemons — structure only. See plans/block-protocol.md for the full block schema.
6.2 Health Monitoring
- Health checks run every 30 seconds
- 3 consecutive failures → interface marked offline → its tools become invisible
- On recovery → tools automatically re-appear
- No user notification for transient state changes
7. Interface Lifecycle
7.1 Pairing
- Human opens brain dashboard → “Generate Pairing Key”
- Chalie generates a one-time key (10-minute expiry) and displays its host:port
- Human enters host:port + key into the interface’s setup
- Interface calls
POST /api/interfaces/pair - Chalie validates key → health-checks interface → fetches capabilities → registers tools
- Chalie returns
interface_id+signal_tokento the interface
POST /api/interfaces/pair request:
| Field | Type | Required | Description |
|---|---|---|---|
pairing_key |
string | yes | One-time key from brain dashboard |
name |
string | yes | Interface display name |
host |
string | yes | Interface hostname/IP |
port |
integer | yes | Interface port (1–65535) |
Response: 201 {"interface_id": "<uuid>", "signal_token": "<token>"}
After pairing, the interface’s capabilities are registered as tools. The LLM sees them alongside innate skills and local tools — it does not know the difference.
7.2 Management Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/interfaces/pairing-key |
Cookie | Generate one-time key (10min TTL) |
| POST | /api/interfaces/pair |
Pairing key | Complete pairing |
| GET | /api/interfaces |
Cookie | List all paired interfaces |
| GET | /api/interfaces/<id> |
Cookie | Get interface details + tools |
| POST | /api/interfaces/<id>/refresh |
Cookie | Re-fetch capabilities |
| DELETE | /api/interfaces/<id> |
Cookie | Unpair and remove all tools |
8. Authentication
| Client Type | Auth Method | Issued When | Scope |
|---|---|---|---|
| Chat UI / Brain dashboard | Session cookie | Login (POST /auth/login) |
Full API access |
| Paired interface | Bearer token | Pairing | Declared signal types + tool execution responses |
| Mobile app | Session cookie | Login | Full API access |
Interfaces authenticate every request with Authorization: Bearer <signal_token>. The token is issued once during pairing (shown once, hash stored). Capability scoping ensures an interface can only emit signal types it declared during pairing.
9. Real-World Examples
9.1 Restaurant (Broadcast Signal)
- Restaurant broadcasts “Closed tonight” to all subscribers
- Each subscribed Chalie receives the signal → world state updated (zero LLM)
- User hasn’t chatted today — reasoning loop enters idle cycle
- World state shows: “Luigi’s closed tonight” (salience 0.82) + “Dinner reservation at 8pm” (salience 0.91)
- Reasoning loop connects the dots → proactive message to user: “Luigi’s is closed tonight. You have a reservation — want me to find alternatives?”
9.2 Hospital (Signals + Messages + Tools)
Signal (broadcast): Hospital broadcasts “Emergency in Wing B — visitor restrictions active.” All subscribed Chalies update world state. Most ignore it. One Chalie’s human has an appointment in Wing B — it surfaces the information.
Message (targeted): Hospital sends POST /api/messages to one Chalie: “Your appointment moved to 3pm.” Chalie reasons, surfaces to user, checks for calendar conflicts.
Tool execution (Chalie → Hospital): User says “Cancel my appointment.” ACT loop selects cancel_appointment tool → POST hospital/execute → hospital confirms → Chalie tells user.
9.3 Stock Exchange (High-Volume Broadcast)
- Chalie subscribes to the exchange’s broadcast stream
- Exchange sends thousands of price updates per minute — each one updates world state (zero cost)
- User asks “How’s my portfolio?” — reasoning loop has fresh world state with current prices
- Chalie responds with a portfolio summary using the latest signal data
9.4 Chat Interface (Human Client)
- Chat app connects to Chalie’s
/ws(session cookie auth) - User speaks → chat transcribes → sends
chatmessage via WebSocket - Chalie reasons (world state includes signals from all paired interfaces)
- Responds via
/wswith integrated context: “Your appointment moved and Luigi’s is closed tonight” - User says “Cancel both” → ACT loop invokes
cancel_appointmenton hospital +cancel_reservationon restaurant - Both interfaces confirm via
/executeresponse → Chalie tells user “Done”
10. Design Principles
Signals bypass the reasoning loop. Written directly to world state. Zero LLM cost regardless of volume. A stock exchange sending 10,000 updates costs nothing.
Interfaces are tool providers, not data consumers. They expose tools. They push signals and messages. They never receive broadcast output. The only data from Chalie to an interface is a specific tool invocation.
WebSocket is for humans, REST is for machines. Human clients use WebSocket for real-time push. Interfaces use REST for signals and messages. Broadcast streams use outbound WebSocket subscription.
Signal tokens reuse wrapper auth. No separate auth system. The signal_token is a standard wrapper bearer token with capability scoping and rate limiting.
Interface tools are normal tools. The LLM prompt does not distinguish source types. Offline interfaces have their tools silently removed; recovery restores them.
No interface-specific code in infrastructure. The protocol is generic. Any application implementing /health, /capabilities, and /execute can pair with Chalie.
11. Key Files
| File | Purpose |
|---|---|
backend/api/signals.py |
Signal ingestion endpoints |
backend/api/interfaces.py |
Interface pairing and management |
backend/api/wrappers.py |
Wrapper token management |
backend/api/websocket.py |
WebSocket endpoint for human clients |
backend/services/world_state_service.py |
World state aggregator (includes external signals) |
backend/services/interface_registry_service.py |
Interface lifecycle (pairing, health, capabilities) |
backend/services/wrapper_auth_service.py |
Bearer token authentication |
backend/services/tool_registry_service.py |
Tool discovery and dispatch |
backend/workers/interface_health_worker.py |
Health check daemon thread |
backend/services/reasoning_loop_service.py |
Signal consumer and continuous reasoning |
docs/18-SIGNAL-CONTRACT.md |
Internal signal contract (cognitive services only) |