The WebSocket message protocol between Codmir clients and the daemon's agent system

Agent Protocol

The daemon's agent system uses a bidirectional WebSocket protocol. Clients send commands and receive events. All messages are JSON with a type field that determines the shape.

Commands (client → daemon)

Start a session

{
  "type": "agent:start",
  "task": "Fix the login bug in auth.ts",
  "cwd": "/Users/you/projects/myapp",
  "model": "claude-sonnet-4-20250514",
  "maxIterations": 50,
  "autoApprove": ["safe"]
}
FieldTypeRequiredDescription
taskstringyesThe user's instruction
cwdstringnoWorking directory (defaults to daemon's cwd)
modelstringnoModel to use (default: claude-sonnet-4-20250514)
maxIterationsnumbernoMax tool-use loops (default: 50)
autoApprovestring[]noDanger levels to auto-approve: safe, moderate, dangerous

Send a follow-up message

{
  "type": "agent:message",
  "sessionId": "sess_abc123",
  "content": "Actually, also fix the logout flow"
}

Approve a tool call

{
  "type": "agent:approve",
  "sessionId": "sess_abc123",
  "toolCallId": "tc_xyz789"
}

Deny a tool call

{
  "type": "agent:deny",
  "sessionId": "sess_abc123",
  "toolCallId": "tc_xyz789",
  "reason": "Don't run rm -rf"
}

Cancel a session

{
  "type": "agent:cancel",
  "sessionId": "sess_abc123"
}

List sessions / Get session

{ "type": "agent:list" }
{ "type": "agent:get", "sessionId": "sess_abc123" }

Events (daemon → client)

Events are broadcast to all connected WebSocket clients. Both the desktop app and IDE extension receive every event.

agent:started

Fired when a new agent session begins.

{
  "type": "agent:started",
  "session": {
    "id": "sess_abc123",
    "task": "Fix the login bug",
    "status": "running",
    "iteration": 0,
    "maxIterations": 50,
    "createdAt": 1718000000000,
    "cwd": "/Users/you/projects/myapp",
    "model": "claude-sonnet-4-20250514"
  }
}

agent:thinking

Streamed thinking/reasoning content. Arrives in chunks as the LLM generates.

{
  "type": "agent:thinking",
  "sessionId": "sess_abc123",
  "content": "Let me look at the auth.ts file first..."
}

agent:tool_call

The agent wants to use a tool.

{
  "type": "agent:tool_call",
  "sessionId": "sess_abc123",
  "toolCall": {
    "id": "tc_xyz789",
    "tool": "read_file",
    "input": { "path": "src/auth.ts" },
    "danger": "safe",
    "requiresApproval": false,
    "approved": true,
    "result": null
  }
}

agent:approval_required

The agent wants to use a dangerous tool and needs user approval. The session pauses until an agent:approve or agent:deny command is received.

{
  "type": "agent:approval_required",
  "sessionId": "sess_abc123",
  "toolCall": {
    "id": "tc_xyz789",
    "tool": "shell",
    "input": { "command": "npm test" },
    "danger": "dangerous",
    "requiresApproval": true,
    "approved": null,
    "result": null
  }
}

agent:shell_output

Real-time output chunks from a running tool (shell commands, Python scripts). Streamed as the process produces output.

{
  "type": "agent:shell_output",
  "sessionId": "sess_abc123",
  "toolCallId": "tc_xyz789",
  "chunk": "PASS src/auth.test.ts\n"
}

agent:tool_result

A tool call has completed.

{
  "type": "agent:tool_result",
  "sessionId": "sess_abc123",
  "result": {
    "toolCallId": "tc_xyz789",
    "tool": "read_file",
    "success": true,
    "output": "import { verify } from 'jsonwebtoken';\n...",
    "error": null,
    "durationMs": 12
  }
}

agent:message

The agent has produced a response message (complete, not streamed).

{
  "type": "agent:message",
  "sessionId": "sess_abc123",
  "message": {
    "role": "assistant",
    "content": "I've fixed the login bug. The issue was...",
    "timestamp": 1718000005000
  }
}

agent:completed

The session has finished successfully.

{
  "type": "agent:completed",
  "session": { "id": "sess_abc123", "status": "completed", "..." : "..." }
}

agent:failed / agent:cancelled

{
  "type": "agent:failed",
  "sessionId": "sess_abc123",
  "error": "Token budget exceeded (500000 tokens)"
}
{
  "type": "agent:cancelled",
  "sessionId": "sess_abc123",
  "message": "Session cancelled by user"
}

agent:list

Response to an agent:list command.

{
  "type": "agent:list",
  "sessions": [
    { "id": "sess_abc123", "task": "Fix login", "status": "completed", "..." : "..." },
    { "id": "sess_def456", "task": "Add tests", "status": "running", "..." : "..." }
  ]
}

Event flow diagram

A typical agent interaction follows this pattern:

Client                          Daemon                          LLM
  │                               │                              │
  │  agent:start(task)            │                              │
  ├──────────────────────────────→│                              │
  │                               │  messages.create(task)       │
  │                               ├─────────────────────────────→│
  │     agent:started(session)    │                              │
  │←──────────────────────────────┤                              │
  │                               │     thinking chunks          │
  │     agent:thinking(chunk)     │←─────────────────────────────┤
  │←──────────────────────────────┤                              │
  │                               │     tool_use: read_file      │
  │     agent:tool_call(tc)       │←─────────────────────────────┤
  │←──────────────────────────────┤                              │
  │                               │  executeTool(read_file)      │
  │     agent:tool_result(r)      │──→ local filesystem          │
  │←──────────────────────────────┤                              │
  │                               │     tool_use: shell (danger) │
  │     agent:approval_required   │←─────────────────────────────┤
  │←──────────────────────────────┤                              │
  │                               │  ⏸ waiting_approval          │
  │  agent:approve(toolCallId)    │                              │
  │──────────────────────────────→│                              │
  │                               │  executeTool(shell)          │
  │     agent:shell_output(chunk) │──→ spawns bash process       │
  │←──────────────────────────────┤                              │
  │     agent:tool_result(r)      │                              │
  │←──────────────────────────────┤                              │
  │                               │  messages.create(results)    │
  │                               ├─────────────────────────────→│
  │                               │     end_turn + text          │
  │     agent:message(content)    │←─────────────────────────────┤
  │←──────────────────────────────┤                              │
  │     agent:completed(session)  │                              │
  │←──────────────────────────────┤                              │

Session states

starting → running ⇄ waiting_approval → completed
                                       → failed
                                       → cancelled
StateDescription
startingSession created, first LLM call about to begin
runningAgent loop active — thinking, calling tools, or waiting for LLM response
waiting_approvalA dangerous tool call needs user approval before execution
completedAgent finished its task (LLM returned end_turn with no tool calls)
failedAn error occurred (token budget, timeout, LLM error)
cancelledUser sent agent:cancel

Limits

LimitDefaultDescription
Max iterations50Maximum tool-use loop cycles per session
Token budget500,000Maximum total tokens (input + output)
Runtime30 minutesMaximum wall-clock time per session
Shell timeout30 secondsPer-command timeout for shell tool
Python timeout60 secondsPer-execution timeout for python tool

Using KernelClient

The @codmir/kernel package exports a KernelClient class that wraps the WebSocket protocol:

import { KernelClient } from '@codmir/kernel/client';

const client = new KernelClient(7700);
client.connect();

// Listen for events
client.onAgentSession((session) => {
  console.log('Session started:', session.id);
});

client.onAgentThinking((sessionId, content) => {
  process.stdout.write(content);
});

client.onAgentToolCall((sessionId, toolCall) => {
  console.log(`Tool: ${toolCall.tool}`, toolCall.input);
});

client.onAgentApproval((sessionId, toolCall) => {
  // Auto-approve safe tools, prompt for dangerous ones
  if (toolCall.danger === 'safe') {
    client.agentApprove(sessionId, toolCall.id);
  }
});

client.onAgentMessage((sessionId, message) => {
  console.log('Response:', message.content);
});

// Start a session
client.agentStart('Fix the login bug in auth.ts', {
  cwd: '/path/to/project',
});