daemon-nagato
daemon-nagato
Nagato is a stateless, crash-safe consumer that receives remote commands from Ably and forwards them to the local daemon for processing.
Core Purpose
Nagato receives commands from Ably and delivers them to the daemon.
When a remote client (e.g., mobile app, web dashboard) wants to send a command to a device, it publishes to the device's Ably channel. Nagato subscribes to this channel, receives the encrypted command, and forwards it to the daemon for execution.
Nagato now subscribes through the local daemon-ably transport socket (~/.unbound/ably.sock).
Nagato does not use Ably SDK credentials, broker tokens, or ABLY_API_KEY directly.
Architecture
┌─────────────────────────────────────────────────────────────────────┐
│ Ably │
│ Channel: remote:{device_id}:commands │
│ │
│ ┌─────────────┬─────────────┬─────────────┬─────────────┐ │
│ │ command-1 │ command-2 │ command-3 │ command-4 │ ... │
│ │ encrypted │ encrypted │ encrypted │ encrypted │ │
│ │ payload │ payload │ payload │ payload │ │
│ └─────────────┴─────────────┴─────────────┴─────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
│ Ably Realtime Subscribe + Publish ACK
▼
┌─────────────────────────────────────────────────────────────────────┐
│ daemon-ably sidecar │
│ │
│ • Owns Ably realtime connection + reconnect behavior │
│ • Restores subscriptions after reconnect │
│ • Pushes message stream over local IPC │
│ │
└─────────────────────────────────────────────────────────────────────┘
│
│ NDJSON IPC (`subscribe.v1`, `message.v1`)
│ ~/.unbound/ably.sock
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Nagato │
│ │
│ • Subscribes to `remote.command.v1` on device-specific channel │
│ • Generates command_id (UUID) for each message │
│ • Forwards encrypted payload to daemon │
│ • Waits for daemon decision (ACK_MESSAGE or DO_NOT_ACK) │
│ • Publishes command ACKs via daemon-ably `publish.ack.v1` │
│ • Handles timeout fail-open behavior │
│ │
└─────────────────────────────────────────────────────────────────────┘
│
│ Unix Domain Socket (binary protocol)
│ ~/.unbound/nagato.sock
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Daemon │
│ │
│ • Receives encrypted payload via CommandFrame │
│ • Decrypts and validates command │
│ • Executes command (start session, send message, etc.) │
│ • Sends DaemonDecisionFrame (ACK_MESSAGE or DO_NOT_ACK) │
│ │
└─────────────────────────────────────────────────────────────────────┘Relationship with Falco
Nagato and Falco are complementary:
| Component | Direction | Purpose |
|---|---|---|
| Nagato | Ably → Daemon | Receives remote commands |
| Falco | Daemon → Ably | Publishes side-effects |
Together they enable bidirectional real-time sync through one shared local transport:
- Commands flow in via Nagato
- Events flow out via Falco
Ably Contract
Nagato currently uses one channel and two event types:
| Direction | Channel | Event | Payload |
|---|---|---|---|
| Remote -> Device | remote:{device_id}:commands | remote.command.v1 | Encrypted command bytes/JSON |
| Device -> Remote | remote:{device_id}:commands | remote.command.ack.v1 | Command ack payload |
Command ACK payload schema:
{
"schema_version": 1,
"command_id": "uuid-v4",
"status": "accepted",
"created_at_ms": 1739030400000,
"result_b64": "optional-base64-daemon-result"
}status is one of:
accepted(daemon returnedACK_MESSAGE)rejected(daemon returnedDO_NOT_ACK)timeout(daemon timed out and Nagato applied fail-open)
Binary Protocol
Nagato communicates with the daemon using a length-prefixed binary protocol over Unix Domain Sockets.
Frame Format
[4 bytes: length (LE u32)][payload bytes]CommandFrame (Nagato -> Daemon)
Offset Size Field
0 4 total_len (LE u32)
4 1 type = 0x01
5 1 flags
6 2 reserved
8 16 command_id (UUID bytes)
24 4 payload_len (LE u32)
28 N encrypted_payloadDaemonDecisionFrame (Daemon -> Nagato)
Offset Size Field
0 4 total_len (LE u32)
4 1 type = 0x02
5 1 decision (0x01=ACK_MESSAGE, 0x02=DO_NOT_ACK)
6 2 reserved
8 16 command_id (UUID bytes)
24 4 result_len (LE u32)
28 N optional_resultTimeout Behavior (Fail-Open)
If the daemon does not respond within the timeout period (default: 15 seconds), Nagato applies fail-open semantics:
- The message is considered processed
- Processing continues with the next message
- This prevents a stuck daemon from blocking the queue
The timeout should be set high enough to allow for:
- Command decryption
- Command execution
- Any downstream processing
Non-Negotiable Invariants
- Content-Agnostic: Nagato never decrypts or rewrites command payloads
- One-In-Flight: Only one command processed at a time
- Fail-Open Timeout: Timeout results in continue (not block)
- Command-ID Correlated ACKs: outbound ACKs are keyed by daemon
command_id - Crash-Safe: No persistent state; Ably handles redelivery
Installation
cd packages/daemon-nagato
go build -o nagato ./cmd/nagatoUsage
# Set required environment variables
export UNBOUND_ABLY_SOCKET="$HOME/.unbound/ably.sock"
# Run Nagato
./nagato --device-id "device-uuid-here"
# With debug logging
./nagato --device-id "device-uuid-here" --debugConfiguration
| Variable | Default | Description |
|---|---|---|
UNBOUND_ABLY_SOCKET | (required) | daemon-ably local IPC socket path |
NAGATO_SOCKET | ~/.unbound/nagato.sock | Unix socket path |
NAGATO_DAEMON_TIMEOUT | 15 | Daemon response timeout in seconds |
Package Structure
packages/daemon-nagato/
├── cmd/nagato/main.go # CLI entry point
├── config/config.go # Configuration management
├── consumer/consumer.go # Ably message consumer
├── client/client.go # Unix socket daemon client
├── courier/courier.go # Main orchestration loop
├── protocol/
│ ├── protocol.go # Binary wire protocol
│ └── protocol_test.go # Protocol tests
├── go.mod
├── go.sum
└── README.mdError Handling
| Error Type | Action |
|---|---|
| daemon-ably socket disconnect | reconnect + restore subscriptions via shared client |
| Daemon connection error | Retry connection on next message |
| Daemon timeout | Fail-open, continue processing |
| Protocol error | Log and continue |
Logging
Nagato uses structured logging with zap. Log levels:
| Level | Use Case |
|---|---|
DEBUG | Frame details, message processing |
INFO | Successful deliveries, connections |
WARN | Timeouts, retries, rejections |
ERROR | Persistent failures |
Enable debug logging with --debug flag.