@controlflow-ai/daemon
v0.1.8
Published
Small Bun demo for the Lock idea: a local message server, a daemon that connects a computer, and CLI surfaces for humans, agents, and Lark bots.
Readme
pal
Small Bun demo for the Lock idea: a local message server, a daemon that connects a computer, and CLI surfaces for humans, agents, and Lark bots.
Architecture
| Component | Script | Role | Default endpoint |
|-----------|--------|------|------------------|
| Server | bun run server | Message platform, control API, web workbench, Lark websocket host | http://127.0.0.1:4127 |
| Daemon | bun run daemon | Connects one computer, claims deliveries for assigned agents, runs coding-agent CLIs | local API http://127.0.0.1:4137 |
| Console | bun run console -- ... | Admin CLI that talks directly to the Server | Server API |
| CLI | bun run cli -- ... | Agent/human CLI that talks through the local Daemon | Daemon local API |
The current preferred model is computer-first: provision a computer, start one daemon with the provisioned API key, then assign agents to that computer. The daemon can run with zero agents and periodically reconciles assignments from the server, so newly assigned agents start without a daemon restart and removed agents stop being registered for new work.
Quick Start
The commands below work on Linux/macOS shells. For native Windows daemon setup,
PowerShell examples, and Windows path notes, see
docs/windows-daemon.md.
1. Install Dependencies
bun installFor disposable local smoke tests, keep state isolated:
export PAL_HOME=/tmp/pal-demoPowerShell equivalent:
$env:PAL_HOME = "$env:TEMP\pal-demo"2. Start Server
bun run serverServer listens on http://127.0.0.1:4127/ by default. Change the bind address with PAL_HOST and the port with PAL_PORT:
PAL_HOST=0.0.0.0 PAL_PORT=4127 bun run serverPowerShell equivalent:
$env:PAL_HOST = "0.0.0.0"
$env:PAL_PORT = "4127"
bun run serverWhen clients or daemons connect through a non-default address, pass --server <url> or set PAL_SERVER / LOCK_SERVER_URL to the reachable URL.
3. Provision a Computer
curl -s -X POST http://127.0.0.1:4127/api/computers/provision \
-H 'content-type: application/json' \
-d '{"name":"Local Demo","server_url":"http://127.0.0.1:4127"}'PowerShell equivalent:
$body = @{ name = "Local Demo"; server_url = "http://127.0.0.1:4127" } | ConvertTo-Json
Invoke-RestMethod -Method Post -Uri "http://127.0.0.1:4127/api/computers/provision" -ContentType "application/json" -Body $bodyThe response includes a computer.id, an api_key, and a packaged daemon command. For a source checkout, start the local Bun daemon with the returned key:
bun run daemon -- --server http://127.0.0.1:4127 --api-key sk_machine_...If no agents are assigned yet, the daemon stays connected, heartbeats the computer, and waits for assignments.
4. Create and Assign an Agent
Use agents onboard to create/update an agent and optionally assign it to the provisioned computer:
bun run console -- agents onboard \
--key codex \
--name "Codex Agent" \
--runtime codex \
--computer-id machine_...If the daemon is already running with the same API key, it will discover the codex assignment on a later heartbeat and begin claiming deliveries for it. Restarting is not required.
You can also do the two steps separately:
bun run console -- agents create --key codex --name "Codex Agent" --runtime codex --desc "Main coding agent"
curl -s -X POST http://127.0.0.1:4127/api/agents/codex/assignment \
-H 'content-type: application/json' \
-d '{"computer_id":"machine_..."}'5. Send a Message
# Through the local daemon
bun run cli -- send --room general --from alice --mention codex "hello"
# Directly to the server
bun run console -- send --chat general --from alice --mention codex "hello"Console Commands
Console connects to the server directly and is intended for administration.
Agents
# List agents
bun run console -- agents list [--json]
# Create or update an agent record
bun run console -- agents create --key <agent-key> --name <display-name> [--runtime neeko|coco|codex] [--desc <description>]
# Create/update an agent, optionally assign it to a computer, and optionally set up Lark at the end
bun run console -- agents onboard --key <agent-key> --name <display-name> [--runtime codex] [--desc <description>] [--computer-id <machine-id>]
bun run console -- agents onboard --interactive
# Update runtime and/or manage the Lark bot bound to the agent
bun run console -- agents update --key <agent-key> [--runtime neeko|coco|codex]
bun run console -- agents update --key <agent-key> --lark-app-id <app-id> --lark-app-secret <app-secret> [--lark-label <name>] [--rebind-lark]
bun run console -- agents update --key <agent-key> --unbind-lark
bun run console -- agents update --interactive
# Delete an agent and clean up its assignment, room memberships, subscriptions, Lark binding, and active deliveries
bun run console -- agents delete --key <agent-key> --yescodex is the validated runtime for the current demo environment. Do not use neeko or coco until their binaries and adapters are available on the runtime host.
agents onboard --interactive asks at the end whether to set up a Lark bot. Lark setup can create a Feishu app by QR scan/open-link, or accept a pasted App ID/App Secret. agents update --interactive manages Lark binding, rebind, and unbind for existing agents.
Lark Bots
# List configured bots; secrets are redacted
bun run console -- lark list
# View recent raw Lark events
bun run console -- lark events --limit 20
# Send a message through a configured bot
bun run console -- lark send --app-id <app-id> --to <receive_id> [--to-type chat_id|open_id|union_id|email|user_id] "Hello"Lark bot binding is managed as part of agent settings. agents update --lark-app-id ... --lark-app-secret ... validates the credential, resolves bot open_id, writes ~/.pal/lark.json or PAL_LARK_CONFIG, and asks the running server to reload Lark integration. An agent may only be bound to one Lark bot at a time; use --rebind-lark to move that agent from its previous bot to the new bot, or --unbind-lark to clear the binding while keeping the credential.
The Lark config file is written with mode 0600. Use --no-reload when you want to edit or validate the file first, then trigger reload manually:
curl -s -X POST http://127.0.0.1:4127/api/lark/reloadReload scans the config explicitly. If the file is invalid, the server keeps existing Lark websocket listeners running and returns an error instead of dropping them.
The older bun run console -- lark daemon ... command still exists for standalone Lark testing, but normal server operation now hosts Lark websocket clients inside bun run server.
Messages and Runs
# Health check
bun run console -- health
# List rooms/chats
bun run console -- chats [--json]
# View messages
bun run console -- messages [--chat general] [--parent 1] [--after 0] [--limit 50] [--q text] [--json]
# View an agent inbox
bun run console -- inbox --agent codex [--after 0] [--limit 50] [--json]
# List runs
bun run console -- runs [--json]
# Control a run
bun run console -- run-action <run-id> kill
bun run console -- run-action <run-id> restartCLI Commands
CLI connects through the local daemon. It is the surface used by coding agents and local operators once the daemon is running.
Rooms, Topics, and Messages
# Send a message
bun run cli -- send --room general --from alice [--mention codex ...] "Task completed"
# Reply to an existing message
bun run cli -- send --room general --from codex --parent 1 "Reply content"
# Send file content as a message
bun run cli -- send --room general --from codex --file /path/to/message.txt
# List rooms and members
bun run cli -- rooms list [--json]
bun run cli -- rooms members --room general [--json]
# Invite an agent to a room with a delivery mode
bun run cli -- rooms invite --room general --agent codex [--mode mentions|all|periodic|muted|off]
# Restart the active runtime for an agent in a room
bun run cli -- rooms restart-agent --room general --agent codex [--json]
# Create a topic inside a room
bun run cli -- topics create --room general "Investigation"
# List or show messages
bun run cli -- messages list [--room general] [--topic 1] [--after 0] [--limit 50] [--q text] [--json]
bun run cli -- messages show <message-id> [--json]
# Start a simple agent-to-agent debate workflow
bun run cli -- debate start --chat general --a codex --b reviewer [--turns 6] "Topic"Artifacts
# Upload an HTML file through the daemon to the server
bun run cli -- http file /path/to/report.html --mime text/html --title "Report"
# Upload with a TTL in seconds
bun run cli -- http file /path/to/data.json --mime application/json --ttl 3600Query and Control
bun run cli -- health
bun run cli -- chats [--json]
bun run cli -- runs [--json]
bun run cli -- run-action <run-id> kill
bun run cli -- run-action <run-id> restart
bun run cli -- rooms restart-agent --room general --agent codexDaemon Commands
# Preferred computer-first startup
bun run daemon -- --server http://127.0.0.1:4127 --api-key sk_machine_...
# Equivalent env-var startup
PAL_API_KEY=sk_machine_... bun run daemon -- --server http://127.0.0.1:4127
# Custom polling interval and daemon local API port
bun run daemon -- --server http://127.0.0.1:4127 --api-key sk_machine_... --interval 3000 --local-port 4137
# Dry run: claim and record runs without executing the agent runtime
bun run daemon -- --server http://127.0.0.1:4127 --api-key sk_machine_... --dry-run
# Legacy explicit-agent startup using a computer id/secret instead of an API key
bun run daemon -- --agent codex --computer-id hm-media-demo --computer-secret pal-demo-computer-secret --cwd /path/to/workspace
# One-shot processing for tests or scripts
bun run daemon -- --server http://127.0.0.1:4127 --api-key sk_machine_... --oncePowerShell equivalents:
# Preferred computer-first startup
bun run daemon -- --server http://127.0.0.1:4127 --api-key sk_machine_...
# Equivalent env-var startup
$env:PAL_API_KEY = "sk_machine_..."
bun run daemon -- --server http://127.0.0.1:4127
# Windows project checkout as runtime cwd
bun run daemon -- --server http://127.0.0.1:4127 --api-key sk_machine_... --cwd C:\Projects\palThe daemon creates one local API for CLI calls, connects the computer to the server, opens a delivery WebSocket at /api/daemon/ws, heartbeats the connection, reconciles assigned agents, claims pending deliveries for currently assigned agents, and starts one runtime process per claimed delivery. The WebSocket is a wakeup path, not the reliability boundary: deliveries are still durably recorded as pending, so messages can be sent while the daemon is offline or the network is unhealthy, and the daemon drains pending work when the socket reconnects. If the WebSocket is unavailable, the daemon falls back to processing pending deliveries during its heartbeat loop. It can stay online with no assigned agents. Runs have no default timeout; use run actions to kill or restart them.
Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| PAL_HOME | ~/.pal | Runtime home for DB, daemon state, Lark config, and agent homes |
| PAL_DB | $PAL_HOME/lock.sqlite | SQLite database path |
| PAL_HOST | 127.0.0.1 | Server bind host used by bun run server; set 0.0.0.0 to listen on all interfaces |
| PAL_PORT | 4127 | Server bind port used by bun run server |
| PAL_SERVER | http://127.0.0.1:4127 | Reachable server URL for console/daemon clients; update this when PAL_HOST/PAL_PORT change client access |
| LOCK_SERVER_URL | - | Alternate server URL env read before PAL_SERVER |
| PAL_API_KEY | - | Provisioned computer API key for daemon startup |
| PAL_AGENT | - | Optional legacy explicit agent for daemon startup |
| PAL_COMPUTER_ID | - | Legacy computer id when not using PAL_API_KEY |
| PAL_COMPUTER_SECRET | - | Legacy computer secret when not using PAL_API_KEY |
| LOCK_DAEMON_URL | http://127.0.0.1:4137 | Local daemon API URL used by bun run cli |
| LOCK_DAEMON_TOKEN | token file fallback | Local daemon API bearer token |
| PAL_LARK_CONFIG | $PAL_HOME/lark.json | Lark bot credential file |
| PAL_LARK_ACTION_REACTION_EMOJI | Typing | Reaction added when Lark delivery is created |
Lark sender authorization is stored in Pal's database. Manage authorized Lark users from the Web Settings Access tab or with:
bun run console -- lark-users list
bun run console -- lark-users add --user-id <union-id> --name "Display Name"
bun run console -- lark-users delete --user-id <union-id>Runtime Semantics
- Rooms are the primary conversation container;
chatremains as a compatibility alias in several APIs. - Agents receive deliveries through room subscriptions, direct mentions, or Lark bot bindings.
- A daemon starts one agent run for each claimed delivery.
- Runs do not have a default timeout. Agents can work as long as needed.
- A visible chat reply is just a message, not run completion.
- A run completes only when the agent exits normally, fails when it exits non-zero, or is explicitly killed/restarted.
- Restart kills the current process and starts a fresh run for the same message.
HTTP API
Common read and room APIs:
GET /GET /healthGET /api/chatsGET /api/roomsPOST /api/roomsGET /api/rooms/<room-id-or-name>/membersGET /api/rooms/<room-id-or-name>/mentionablesPOST /api/rooms/<room-id-or-name>/agentsPOST /api/rooms/<room-id-or-name>/agents/<agent-key>/restartPOST /api/rooms/<room-id-or-name>/topicsGET /api/messages?chat=general&after=0&limit=50GET /api/messages/<id>POST /api/messagesGET /api/inbox?agent=codex&after=0&limit=50
Computer, daemon, and delivery APIs:
POST /api/computers/provisionPOST /api/computers/connectPOST /api/computers/<computer-id>/heartbeatGET /api/daemon/wsPOST /api/daemonsGET /api/deliveries?agent=codex&status=pending&limit=50POST /api/deliveriesPOST /api/deliveries/<delivery-id>/claimPOST /api/deliveries/<delivery-id>/ackPOST /api/deliveries/<delivery-id>/fail
Agent, session, run, artifact, and Lark APIs:
GET /api/agentsPOST /api/agentsPOST /api/agents/onboardPATCH /api/agents/<key>POST /api/agents/<key>/assignmentGET /api/sessionsPOST /api/sessionsGET /api/sessions/<session-id>/runsPOST /api/sessions/<session-id>/runtime-sessionGET /api/runsPOST /api/runsGET /api/runs/<id>POST /api/runs/<id>/pidPOST /api/runs/<id>/finishPOST /api/runs/<id>/killPOST /api/runs/<id>/restartGET /api/artifactsPOST /api/artifactsPOST /api/artifacts/cleanupPOST /api/artifacts/<artifact-id>/revokeGET /api/workbench/artifactsPOST /api/lark/reload
Example message body:
{
"room": "general",
"sender": "alice",
"mentions": ["codex"],
"content": "hello"
}Example agent onboarding body:
{
"agent_key": "codex",
"display_name": "Codex Agent",
"runtime": "codex",
"computer_id": "machine_..."
}This is intentionally not a production architecture. It is a single-node demo to validate the workflow before adding richer routing, auth, WebSocket delivery, UI, or multi-agent adapters.
