TypeScript SDK
The official TypeScript SDK for CntrlNode. Works in Node.js, Bun, Deno, and browser environments.
Install
npm install @cntrlnode/sdk
# or
pnpm add @cntrlnode/sdk
# or
yarn add @cntrlnode/sdk
Connect
import { CntrlNodeClient } from '@cntrlnode/sdk'
const client = new CntrlNodeClient({
baseUrl: 'http://localhost:7474',
apiKey: 'your-api-key', // optional — omit if auth is disabled
})
State Store
// Set a value (with optional TTL in seconds)
await client.state.set('workflow-1', 'results', { summary: 'Q4 up 12%' }, 3600)
// Get a value
const { value, version } = await client.state.get('workflow-1', 'results')
// Delete
await client.state.delete('workflow-1', 'results')
// List all keys in a workflow namespace
const { keys } = await client.state.list('workflow-1')
// Optimistic write (throws VERSION_CONFLICT on race)
await client.state.setWithVersion('workflow-1', 'counter', value + 1, version)
// Subscribe to changes (SSE)
const es = client.state.subscribe('workflow-1', 'results', (event) => {
console.log('changed:', event.newValue, 'v' + event.version)
})
es.close() // unsubscribe
Task Bus
// Submit a task
const task = await client.tasks.submit({
workflowId: 'workflow-1',
agentId: 'researcher-1',
payload: { query: 'Summarise Q4 report' },
})
// Submit a child task
const child = await client.tasks.submit({
workflowId: 'workflow-1',
agentId: 'writer-1',
parentId: task.id,
payload: { draft: true },
})
// Idempotent submission (safe to call multiple times)
const task = await client.tasks.submit({
workflowId: 'workflow-1',
agentId: 'researcher-1',
idempotencyKey: 'research-q4-2024',
payload: {},
})
// Get status
const t = await client.tasks.get(task.id)
console.log(t.status) // SUBMITTED | ACCEPTED | IN_PROGRESS | COMPLETED | FAILED | CANCELLED
// List all tasks in a workflow
const { tasks } = await client.tasks.list('workflow-1')
// Cancel (recursively cancels all child tasks)
await client.tasks.cancel(task.id)
// Lifecycle (called by the receiving agent)
await client.tasks.accept(task.id)
await client.tasks.complete(task.id, { result: 'done' })
await client.tasks.fail(task.id, 'Connection timed out')
Agent Registry
// Register
await client.registry.register({
id: 'researcher-1',
tags: ['research', 'web-search', 'pdf'],
model: 'claude-sonnet-4-5',
endpoint: 'http://my-agent:8080',
maxConcurrency: 3,
})
// Heartbeat (keep the agent healthy)
await client.registry.heartbeat('researcher-1')
// Auto-heartbeat every 15s
const stop = client.registry.startHeartbeat('researcher-1', 15_000)
// Call stop() to cancel
// Discover by tags (must match ALL tags)
const { agents } = await client.registry.discover({ tags: ['research', 'pdf'] })
// Semantic discovery (requires Ollama)
const { agents } = await client.registry.discover({
query: 'agent that reads PDFs and extracts tables',
topK: 3,
})
// List all agents
const { agents } = await client.registry.list()
// Get one agent
const agent = await client.registry.get('researcher-1')
// Deregister
await client.registry.deregister('researcher-1')
Complete agent example
import { CntrlNodeClient } from '@cntrlnode/sdk'
const client = new CntrlNodeClient({ baseUrl: 'http://localhost:7474' })
async function runAgent() {
// Register and start heartbeat
await client.registry.register({
id: 'my-agent',
tags: ['summarize'],
})
const stopHeartbeat = client.registry.startHeartbeat('my-agent', 15_000)
// Poll for tasks (in production, use a webhook or SSE)
setInterval(async () => {
const { tasks } = await client.tasks.list('workflow-1')
const mine = tasks.filter(t => t.agentId === 'my-agent' && t.status === 'SUBMITTED')
for (const task of mine) {
await client.tasks.accept(task.id)
try {
const result = await doWork(task.payload)
await client.tasks.complete(task.id, result)
} catch (err) {
await client.tasks.fail(task.id, err.message)
}
}
}, 2000)
}
Types
interface Task {
id: string
workflowId: string
agentId: string
parentId?: string
payload: unknown
status: 'SUBMITTED' | 'ACCEPTED' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED' | 'CANCELLED'
result?: unknown
error?: string
createdAt: string
updatedAt: string
}
interface Agent {
id: string
tags: string[]
model?: string
endpoint?: string
maxConcurrency?: number
healthy: boolean
registeredAt: string
lastHeartbeat: string
}
interface StateEntry {
value: unknown
version: number
}