Connect your AI agents to ClockOn in 5 lines of code. Available for Python and TypeScript/Node.js.
ClockOn is a local-first desktop application that visualises AI agents as employees in a 2D pixel-art virtual office. You are the CEO. Your agents clock on, take tasks, report progress, and chat — all visible in real time.
Agents connect to ClockOn via a WebSocket server running on localhost:9746. The connection is authenticated using a session token stored at ~/.clockon/.session_token, which is generated automatically when ClockOn starts.
~/.clockon/.session_token.The official SDKs for Python and TypeScript handle all connection, authentication, and message formatting automatically. You can also build a custom integration using the Protocol Reference below.
pip install clockon-sdk
Requires Python 3.8+ and the websockets library (installed automatically).
npm install @clockon/sdk
Requires Node.js 18+ and the ws library (installed automatically).
from clockon_sdk import Agent
agent = Agent("Writer", role="writer")
agent.connect()
agent.clock_on()
agent.report_status("working", "Writing blog post...")
# When done
agent.report_task_complete("task-123", "Here is the post...")
agent.clock_off()
import { Agent } from '@clockon/sdk';
const agent = new Agent('Writer', { role: 'writer' });
await agent.connect();
agent.clockOn();
agent.reportStatus('working', 'Writing blog post...');
// When done
agent.reportTaskComplete('task-123', 'Here is the post...');
agent.clockOff();
Agent(name, role="assistant", **kwargs)
new Agent(name: string, options?: AgentOptions)
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
string |
required | Display name in the office |
role |
string |
"assistant" |
Agent role (e.g. "writer", "coder", "researcher") |
title |
string |
Same as name |
Job title shown on agent card |
department |
string |
"General" |
Department grouping |
capabilities |
string[] |
[] |
List of capability tags |
model |
string |
"" |
LLM model identifier (e.g. "claude-sonnet-4-20250514") |
provider |
string |
"" |
LLM provider (e.g. "anthropic", "openai") |
system_prompt |
string |
"" |
Agent's system prompt |
avatar_color |
string |
"#2D8B46" |
Hex colour for the agent's avatar |
Connect to the ClockOn WebSocket server.
agent.connect(url="ws://localhost:9746", token=None)
await agent.connect(url?: string, opts?: { token?: string })
| Parameter | Type | Default | Description |
|---|---|---|---|
url |
string |
"ws://localhost:9746" |
WebSocket server URL |
token |
string |
Auto-read from file | Session authentication token. If omitted, read from ~/.clockon/.session_token |
Returns: void (Python) / Promise<void> (TypeScript)
Clock on — the agent appears in the office and is marked as available.
agent.clock_on()
agent.clockOn()
Returns: void
Clock off — the agent leaves the office.
agent.clock_off()
agent.clockOff()
Returns: void
Report the agent's current status. This updates the agent's visual state in the office.
agent.report_status(status, message="", task=None)
agent.reportStatus(status: string, message?: string, task?: TaskInfo)
| Parameter | Type | Default | Description |
|---|---|---|---|
status |
string |
required | One of the status values |
message |
string |
"" |
Human-readable status message |
task |
object |
None |
Current task info: { taskId, title, progress } |
Returns: void
agent.report_status("working", "Writing chapter 3...", task={
"taskId": "task-123",
"title": "Write blog post",
"progress": 60
})
Report that a task has been completed.
agent.report_task_complete(task_id, output, tokens_used=0, cost=0.0)
agent.reportTaskComplete(taskId: string, output: string, opts?: TaskCompleteOptions)
| Parameter | Type | Default | Description |
|---|---|---|---|
task_id |
string |
required | ID of the completed task |
output |
string |
required | Task output content |
tokens_used |
int |
0 |
Number of tokens consumed |
cost |
float |
0.0 |
Dollar cost of the task |
status |
string |
"completed" |
Override status (e.g. "failed") |
output_type |
string |
"text" |
Output format ("text", "markdown", "code") |
duration |
int |
0 |
Duration in milliseconds |
Returns: void
Register a callback for generic commands from ClockOn (pause, resume, clock_off).
def handle_command(command):
print(f"Received command: {command['type']}")
agent.on_command(handle_command)
agent.onCommand((command) => {
console.log(`Received command: ${command.type}`);
});
Callback receives: { type: string, ... }
Returns: void
Register a callback for task assignments from the CEO.
def handle_task(task):
print(f"Assigned task: {task['title']}")
agent.report_status("working", f"Working on: {task['title']}")
# ... do the work ...
agent.report_task_complete(task["taskId"], "Done!")
agent.on_task(handle_task)
agent.onTask((task) => {
console.log(`Assigned task: ${task.title}`);
agent.reportStatus('working', `Working on: ${task.title}`);
// ... do the work ...
agent.reportTaskComplete(task.taskId, 'Done!');
});
Callback receives: { taskId: string, title: string, description?: string }
Returns: void
Register a callback for chat messages from the CEO.
def handle_chat(message):
print(f"CEO says: {message['content']}")
agent.on_chat(handle_chat)
agent.onChat((message) => {
console.log(`CEO says: ${message.content}`);
});
Callback receives: { content: string, ... }
Returns: void
Disconnect from ClockOn and close the WebSocket connection.
agent.disconnect()
agent.disconnect();
Returns: void
The ClockOn protocol uses JSON messages over WebSocket. This section documents the wire format for developers building custom SDK implementations.
ws://localhost:9746{ "type": "auth", "token": "<session_token>" }Every message uses this envelope format:
{
"agent_id": "uuid",
"type": "message_type",
"payload": { ... },
"timestamp": "2026-03-01T12:00:00.000Z"
}
| Field | Type | Description |
|---|---|---|
agent_id | string | UUID of the sending agent |
type | string | One of the message types below |
payload | object | Type-specific payload data |
timestamp | string | ISO 8601 timestamp |
Sent once after authentication. Registers the agent in the office.
{
"type": "register",
"payload": {
"id": "uuid",
"name": "Writer",
"role": "writer",
"title": "Writer",
"department": "General",
"avatar": { "color": "#2D8B46" },
"capabilities": [],
"model": "claude-sonnet-4-20250514",
"provider": "anthropic",
"systemPrompt": "You are a writer...",
"createdAt": "2026-03-01T12:00:00.000Z"
}
}
{
"type": "punch",
"payload": {
"agentId": "uuid",
"type": "clock_on",
"timestamp": "2026-03-01T12:00:00.000Z"
}
}
Punch types: clock_on, clock_off.
{
"type": "status_update",
"payload": {
"agentId": "uuid",
"status": "working",
"message": "Writing blog post...",
"currentTask": {
"taskId": "task-123",
"title": "Write blog post",
"progress": 60
},
"tokensUsedToday": 1500,
"costToday": 0.12
}
}
{
"type": "task_result",
"payload": {
"taskId": "task-123",
"agentId": "uuid",
"status": "completed",
"output": "Here is the blog post...",
"outputType": "text",
"tokensUsed": 2400,
"cost": 0.08,
"duration": 45000,
"timestamp": "2026-03-01T12:01:00.000Z"
}
}
Commands are sent from ClockOn to the agent. Handle these with onCommand(), onTask(), or onChat().
{
"type": "command",
"payload": {
"type": "assign_task",
"taskId": "task-456",
"title": "Write a blog post about AI agents",
"description": "1000 words, casual tone"
}
}
| Command Type | Description | Callback |
|---|---|---|
assign_task | Assign a task to the agent | onTask() |
chat | Send a chat message | onChat() |
pause | Pause the agent | onCommand() |
resume | Resume the agent | onCommand() |
clock_off | Request the agent to clock off | onCommand() |
Use these values with reportStatus() to update the agent's visual state in the office.
A full agent that receives tasks, reports progress, and delivers results.
from clockon_sdk import Agent
import time
agent = Agent("Researcher", role="researcher", department="R&D")
agent.connect()
agent.clock_on()
def handle_task(task):
task_id = task["taskId"]
title = task.get("title", "Unknown task")
agent.report_status("working", f"Researching: {title}", task={
"taskId": task_id,
"title": title,
"progress": 0
})
# Simulate work with progress updates
for progress in [25, 50, 75, 100]:
time.sleep(2)
agent.report_status("working", f"Researching: {title}", task={
"taskId": task_id,
"title": title,
"progress": progress
})
agent.report_task_complete(
task_id=task_id,
output="Research complete. Here are the findings...",
tokens_used=3200,
cost=0.15
)
agent.report_status("clocked_on", "Ready for next task")
agent.on_task(handle_task)
# Keep the agent running
print("Agent is online. Press Ctrl+C to disconnect.")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
agent.clock_off()
agent.disconnect()
An agent that responds to chat messages and handles task assignments.
import { Agent } from '@clockon/sdk';
const agent = new Agent('Assistant', {
role: 'assistant',
department: 'Support'
});
await agent.connect();
agent.clockOn();
agent.onChat((message) => {
const content = message.content as string;
console.log(`CEO: ${content}`);
agent.reportStatus('working', 'Thinking...');
// Process the message and respond
setTimeout(() => {
agent.reportStatus('clocked_on', 'Ready');
}, 2000);
});
agent.onTask((task) => {
const taskId = task.taskId as string;
const title = task.title as string;
agent.reportStatus('working', `Working on: ${title}`);
// Do the work...
setTimeout(() => {
agent.reportTaskComplete(taskId, 'Task completed!', {
tokensUsed: 1500,
cost: 0.05
});
agent.reportStatus('clocked_on', 'Ready for next task');
}, 5000);
});
console.log('Agent is online. Press Ctrl+C to disconnect.');
process.on('SIGINT', () => {
agent.clockOff();
agent.disconnect();
process.exit(0);
});
ClockOn detects CLI AI tools already installed on your machine and lets you hire them as office employees. Supported tools: Claude Code (Anthropic), Codex CLI (OpenAI), Gemini CLI (Google), OpenCode, and Aider. Any unknown CLI tool gets a generic fallback profile.
On launch, ClockOn scans your PATH for known CLI binaries. If found and authenticated, a "Your first hire is ready" modal appears. Click Hire, give them a name, and they appear at a desk. You can customise their name, title, role, and department during hiring.
clockon wrap are view-only. You watch their token usage and status, but interact with them directly in your terminal.The bridge is a transparent API proxy that sits between your CLI tool and its AI provider. It intercepts HTTP requests, forwards them unmodified, and extracts exact token counts from the responses. Zero latency impact. Your API keys pass through opaquely — the bridge never stores them.
Run clockon wrap claude (or any supported CLI tool) in your terminal. The bridge starts a local proxy, sets the appropriate environment variable (e.g. ANTHROPIC_BASE_URL), and launches the tool. Token counts flow to ClockOn's timesheet and CEO Dashboard automatically.
clockon wrap claude
Headless mode: the bridge works even when ClockOn isn't running — it accumulates token data locally and syncs when ClockOn launches.
Manage your office from the terminal:
clockon agent create --name Maya --role writer --dept Marketing
Create a new agent programmatically.
clockon agent list
List all agents with their status.
clockon agent assign --agent Maya --task "Write a blog post about AI agents"
Assign a task to an agent.
clockon wrap claude
Observe a Claude Code session with automatic token tracking.
clockon wrap ollama run llama3.1:8b
Observe a local Ollama session.
These commands can also be used by the COO agent (see COO Pattern) to manage the office on your behalf.
Your first hire can manage the entire office. The COO (Chief Operating Officer) template includes ClockOn CLI commands as tools in its system prompt. When you tell the COO "hire me a blog writer," it runs clockon agent create to set up the agent automatically.
The COO can hire any of these and assign them tasks — or you can create custom agents with any role.
Each CLI agent has a workspace directory at ~/.clockon/agents/{name}/workspace/. Files created or modified by the agent during a task appear in the Agent Panel's Tasks tab with filename, size, and timestamp. Click a file to open it in your system's default file viewer.
The workspace watcher detects changes within 2 seconds of a file being created or modified.