Wake Delivery
Wake delivery is how Chronos hands a due alarm to the agent. Chronos never talks to Fly or the daemon directly; it reuses the router's battle-tested EnsureStarted + waitDaemonReady + 6PN reverse-proxy path via one new router surface: POST /internal/wake.
Wake delivery is how Chronos hands a due alarm to the agent. Chronos never talks to Fly or the daemon directly; it reuses the router's battle-tested EnsureStarted + waitDaemonReady + 6PN reverse-proxy path via one new router surface: POST /internal/wake.
Source file: internal/wake/wake.go.
The Waker interface
type Waker interface {
Wake(ctx context.Context, req Request) error
}
The Waker interface abstracts the delivery mechanism. The concrete implementation is HTTPWaker, which POSTs to the router's internal wake endpoint. The interface returns a non-nil error on any non-2xx response so the dispatch retry ladder can act (honest failure, invariant i6).
Wake request
The Request struct is the body POSTed to the router's /internal/wake endpoint:
type Request struct {
UserID string `json:"user_id"`
ConversationID string `json:"conversation_id,omitempty"`
Message string `json:"message"`
Payload json.RawMessage `json:"payload,omitempty"`
AlarmID string `json:"alarm_id"`
Origin string `json:"origin"` // always "chronos"
}
| Field | Purpose |
|---|---|
user_id | The Supabase user UUID extracted from the agent's DID. The router uses this to resolve the user's machine. |
conversation_id | The conversation to resume into. Empty means a fresh conversation. |
message | The wake_message from the alarm, delivered verbatim (invariant i4). This is the contextful turn the agent receives. |
payload | Opaque JSON state from the alarm, echoed back so the agent can recover context without re-derivation. |
alarm_id | The alarm's UUID. Rides along for downstream deduplication (invariant i3) and tagging as a timer wake. |
origin | Always "chronos". Set automatically by HTTPWaker.Wake. |
HTTPWaker
type HTTPWaker struct {
URL string
Token string
Client *http.Client
}
The HTTPWaker is constructed with the router's internal wake URL and the shared wake token:
func New(url, token string) *HTTPWaker {
return &HTTPWaker{
URL: url,
Token: token,
Client: &http.Client{Timeout: 60 * time.Second},
}
}
The default timeout is 60 seconds, which accounts for the router's EnsureStarted call (cold-starting a suspended Fly machine can take 30-45 seconds).
Wake flow
When the dispatch worker fires an alarm, the complete delivery path is:
1. dispatch.Worker.fire()
Calls waker.Wake() with the alarm's user_id, conversation_id, wake_message, payload
2. HTTPWaker.Wake()
POSTs to router /internal/wake with Bearer wake-token auth
Sets origin="chronos" automatically
3. Router receives /internal/wake
Resolves user_id -> machine
Calls EnsureStarted (cold-start if suspended)
Waits for daemon readiness (waitDaemonReady)
POSTs {message, conversation_id, payload} to daemon /chat over Fly 6PN
4. Daemon /chat
Injects the wake_message as a new chat turn in the conversation
The agent sees the message and resumes with full context
Chronos is responsible only for steps 1-2. The router handles all machine lifecycle and network routing.
Error handling
Any transport failure or non-2xx response from the router is returned as an error. The response body is surfaced (truncated to 300 chars) for honest failure recording in the alarm's last_error field.
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("wake: router returned %d: %s", resp.StatusCode, truncate(respBody, 300))
}
The dispatch worker's retry ladder acts on these errors: retry with backoff (Tier 1), permanently fail for once alarms (Tier 2), or skip-and-advance for cron alarms (Tier 3).
Auth
The wake request uses a shared bearer token (WakeToken in config, matching the router's ROUTER_WAKE_TOKEN). This is transport-level auth only: it proves the request comes from a legitimate Chronos instance. There is no principal-level auth on the wake path because Chronos has already verified the agent's DID at alarm creation time and the user_id is derived from that verified identity.
Key invariant
Chronos never fabricates a successful wake. If the router returns an error, the alarm is retried or marked failed. The last_error field on the alarm records exactly what went wrong for observability.
