Playbooks: Common Scenario Scripts
January 23, 2026 · View on GitHub
This page breaks down the most common usage scenarios from a practical perspective, providing mental maps, key APIs, example files, and considerations. Example code is in the examples/ directory and can be run directly with ts-node.
1. Collaborative Inbox (Event-Driven UI)
- Goal: Persistent single Agent, UI displays text/tool progress via Progress stream, Monitor for lightweight alerts.
- Example:
examples/01-agent-inbox.ts - Run:
npm run example:agent-inbox - Key Steps:
Agent.create+agent.subscribe(['progress'])pushes text increments.- Use
bookmark/cursorfor checkpoint replay. agent.on('tool_executed')/agent.on('error')writes governance events to logs or monitoring.agent.todoManagerfor auto-reminders, UI can display Todo panel.
- Considerations:
- Expose Progress stream to frontend via SSE/WebSocket.
- Enable
exposeThinkingin template metadata if UI needs thinking process.
// Basic event subscription
for await (const envelope of agent.subscribe(['progress'])) {
if (envelope.event.type === 'text_chunk') {
process.stdout.write(envelope.event.delta);
}
if (envelope.event.type === 'done') {
break;
}
}
2. Tool Approval & Governance
- Goal: Approval for sensitive tools (e.g.,
bash_run, database writes); combine with Hooks for policy guards. - Example:
examples/02-approval-control.ts - Run:
npm run example:approval - Key Steps:
- Configure
permissionin template (e.g.,mode: 'approval'+requireApprovalTools). - Subscribe to
agent.on('permission_required'), push approval tasks to business system. - Approval UI calls
agent.decide(id, 'allow' | 'deny', note). - Combine with
HookManager'spreToolUse/postToolUsefor finer-grained policies (path guards, result truncation).
- Configure
- Considerations:
- Agent is at
AWAITING_APPROVALbreakpoint during approval; SDK auto-resumes after decision. - Denying a tool automatically writes
tool_result, UI can prompt retry strategies.
- Agent is at
// Permission configuration
const template = {
id: 'secure-runner',
permission: {
mode: 'approval',
requireApprovalTools: ['bash_run'],
},
// Hook for additional guards
hooks: {
preToolUse(call) {
if (call.name === 'bash_run' && /rm -rf|sudo/.test(call.args.cmd)) {
return { decision: 'deny', reason: 'Command matches forbidden keywords' };
}
},
},
};
// Approval handling
agent.on('permission_required', async (event) => {
const decision = await getApprovalFromAdmin(event.call);
await event.respond(decision, { note: 'Approved by admin' });
});
3. Multi-Agent Team Collaboration
- Goal: One Planner coordinates multiple Specialists, all Agents persistent and forkable.
- Example:
examples/03-room-collab.ts - Run:
npm run example:room - Key Steps:
- Use singleton
AgentPoolto manage Agent lifecycle (create/resume/fork). - Use
Roomfor broadcast/mention messages; messages use[from:name]pattern for collaboration. - Sub-Agents launched via
task_runtool or explicitpool.create. - Use
agent.snapshot()+agent.fork()to fork at Safe-Fork-Points.
- Use singleton
- Considerations:
- Template's
runtime.subagentscan limit dispatchable templates and depth. - Persist lineage (SDK writes to metadata by default) for audit and replay.
- Disable
watchFilesin template if not monitoring external files.
- Template's
const pool = new AgentPool({ dependencies: deps, maxAgents: 10 });
const room = new Room(pool);
const planner = await pool.create('agt-planner', { templateId: 'planner', ... });
const dev = await pool.create('agt-dev', { templateId: 'executor', ... });
room.join('planner', planner.agentId);
room.join('dev', dev.agentId);
// Broadcast to room
await room.say('planner', 'Hi team, let us audit the repository. @dev please execute.');
await room.say('dev', 'Acknowledged, working on it.');
4. Scheduling & System Reminders
- Goal: Agent executes periodic tasks, monitors file changes, sends system reminders during long-running operations.
- Example:
examples/04-scheduler-watch.ts - Run:
npm run example:scheduler - Key Steps:
const scheduler = agent.schedule(); scheduler.everySteps(N, callback)registers step triggers.- Use
agent.remind(text, options)for system-level reminders (via Monitor, doesn't pollute Progress). - FilePool monitors written files by default, combine
monitor.file_changedwithscheduler.notifyExternalTriggerfor auto-response. - Todo with
remindIntervalStepsfor periodic reviews.
- Considerations:
- Keep scheduled tasks idempotent, follow event-driven principles.
- For high-frequency tasks, combine with external Cron and call
scheduler.notifyExternalTrigger.
5. Database Persistence
- Goal: Persist Agent state to SQLite or PostgreSQL for production deployments.
- Example:
examples/db-sqlite.ts,examples/db-postgres.ts - Key Steps:
- Use
createExtendedStorefactory function to create store. - Pass store to Agent dependencies.
- Use Query APIs for session management and analytics.
- Use
import { createExtendedStore, SqliteStore } from '@shareai-lab/kode-sdk';
// Create SQLite store
const store = createExtendedStore({
type: 'sqlite',
dbPath: './data/agents.db',
fileStoreBaseDir: './data/files',
}) as SqliteStore;
// Use with Agent
const agent = await Agent.create(
{ templateId: 'my-agent', ... },
{ store, ... }
);
// Query APIs
const sessions = await store.querySessions({ limit: 10 });
const stats = await store.aggregateStats(agent.agentId);
6. Combined: Approval + Collaboration + Scheduling
- Scenario: Code review bot, Planner splits tasks and assigns to Specialists, tool operations need approval, scheduled reminders ensure SLA.
- Implementation:
- Planner template: Has
task_runtool and scheduling hooks, auto-patrol each morning. - Specialist template: Focuses on
fs_*+todo_*tools, approval only forbash_run. - Unified approval service: Listens to all Agent Control events, integrates with enterprise IM/approval workflow.
- Room collaboration: Planner delivers tasks via
@executor, executor reports back via@planner. - SLA monitoring: Monitor events feed into observability pipeline (Prometheus/ELK/Datadog).
- Scheduled reminders: Use Scheduler to periodically check todos or external system signals.
- Planner template: Has
Quick API Reference
| Category | API |
|---|---|
| Events | agent.subscribe(['progress']), agent.on('error', handler), agent.on('tool_executed', handler) |
| Approval | permission_required → event.respond() / agent.decide() |
| Multi-Agent | new AgentPool({ dependencies, maxAgents }), const room = new Room(pool) |
| Fork | const snapshot = await agent.snapshot(); const fork = await agent.fork(snapshot); |
| Scheduling | agent.schedule().everySteps(10, ...), scheduler.notifyExternalTrigger(...) |
| Todo | agent.getTodos() / agent.setTodos() / todo_read / todo_write |
| Database | createExtendedStore({ type: 'sqlite', ... }), store.querySessions() |
References
7. CLI Agent Application
Build command-line AI assistants like Claude Code or Cursor.
Minimal CLI Agent
// cli-agent.ts
import { Agent, AnthropicProvider, JSONStore, LocalSandbox } from '@shareai-lab/kode-sdk';
import * as readline from 'readline';
async function main() {
const store = new JSONStore('./.cli-agent');
const provider = new AnthropicProvider(process.env.ANTHROPIC_API_KEY!);
const sandbox = new LocalSandbox({ workDir: process.cwd() });
const agent = await Agent.create({
templateId: 'cli-assistant',
model: provider,
sandbox: { kind: 'local', workDir: process.cwd() },
}, {
store,
templateRegistry,
sandboxFactory,
toolRegistry,
});
// Stream output to terminal using subscribe
(async () => {
for await (const envelope of agent.subscribe(['progress'])) {
if (envelope.event.type === 'text_chunk') {
process.stdout.write(envelope.event.delta);
}
if (envelope.event.type === 'tool:start') {
console.log(`\n[Running: ${envelope.event.call.name}]`);
}
if (envelope.event.type === 'done') {
break;
}
}
})();
// Interactive loop
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
console.log('CLI Agent ready. Type your message (Ctrl+C to exit)\n');
const askQuestion = () => {
rl.question('You: ', async (input) => {
if (input.trim()) {
console.log('\nAssistant: ');
await agent.complete(input); // complete() handles send + wait
console.log('\n');
}
askQuestion();
});
};
askQuestion();
}
main().catch(console.error);
Production CLI with Session Management
// production-cli.ts
import { Agent, AgentPool, JSONStore } from '@shareai-lab/kode-sdk';
import * as path from 'path';
import * as os from 'os';
import * as readline from 'readline';
import { program } from 'commander';
const DATA_DIR = path.join(os.homedir(), '.my-cli-agent');
const store = new JSONStore(DATA_DIR);
async function createDependencies() {
return {
store,
templateRegistry: /* ... */,
sandboxFactory: /* ... */,
toolRegistry: /* ... */,
};
}
async function main() {
program
.option('-s, --session <id>', 'Session ID to resume', 'default')
.option('-n, --new', 'Start new session (ignore existing)')
.option('-l, --list', 'List all sessions')
.parse();
const opts = program.opts();
const deps = await createDependencies();
// List sessions
if (opts.list) {
const sessions = await store.list();
console.log('Available sessions:');
sessions.forEach(s => console.log(` - ${s}`));
return;
}
const pool = new AgentPool({ dependencies: deps, maxAgents: 5 });
const sessionId = opts.session;
// Resume or create agent
let agent: Agent;
const exists = await store.exists(sessionId);
if (exists && !opts.new) {
console.log(`Resuming session: ${sessionId}`);
agent = await pool.resume(sessionId, { templateId: 'cli-assistant' });
} else {
console.log(`Starting new session: ${sessionId}`);
agent = await pool.create(sessionId, { templateId: 'cli-assistant' });
}
// Event handlers
for await (const envelope of agent.subscribe(['progress'])) {
switch (envelope.event.type) {
case 'text_chunk':
process.stdout.write(envelope.event.delta);
break;
case 'tool:start':
console.log(`\n[Tool: ${envelope.event.call.name}]`);
break;
case 'done':
console.log('\n');
break;
}
}
// Interactive loop with special commands
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
const processInput = async (input: string) => {
const trimmed = input.trim();
// Special commands
if (trimmed === '/exit' || trimmed === '/quit') {
console.log('Goodbye!');
process.exit(0);
}
if (trimmed === '/clear') {
// Fork to create fresh context
const snapshot = await agent.snapshot('clear-point');
agent = await agent.fork(snapshot); // snapshot is already a SnapshotId
console.log('Context cleared.');
return;
}
if (trimmed === '/status') {
const status = agent.status();
console.log(`Session: ${status.agentId}`);
console.log(`Steps: ${status.stepCount}`);
console.log(`State: ${status.state}`);
return;
}
// Normal message
if (trimmed) {
console.log('\nAssistant: ');
await agent.complete(trimmed);
}
};
console.log('Ready. Commands: /exit, /clear, /status\n');
rl.on('line', async (line) => {
await processInput(line);
rl.prompt();
});
rl.prompt();
}
main().catch(console.error);
8. Desktop App (Electron)
Build desktop AI applications with Electron or Tauri.
Architecture Overview
┌────────────────────────────────────────────┐
│ Electron App │
│ ┌──────────────────────────────────────┐ │
│ │ Renderer Process │ │
│ │ ┌──────────────────────────────┐ │ │
│ │ │ React UI │ │ │
│ │ │ - Chat interface │ │ │
│ │ │ - Tool output display │ │ │
│ │ │ - Settings panel │ │ │
│ │ └──────────────┬───────────────┘ │ │
│ └─────────────────┼────────────────────┘ │
│ │ IPC │
│ ┌─────────────────▼────────────────────┐ │
│ │ Main Process │ │
│ │ ┌──────────────────────────────┐ │ │
│ │ │ AgentPool │ │ │
│ │ │ - Agent lifecycle │ │ │
│ │ │ - Event distribution │ │ │
│ │ │ - Store management │ │ │
│ │ └──────────────────────────────┘ │ │
│ │ ┌──────────────────────────────┐ │ │
│ │ │ JSONStore │ │ │
│ │ └──────────────┬───────────────┘ │ │
│ └─────────────────┼────────────────────┘ │
└────────────────────┼────────────────────────┘
│
┌──────▼──────┐
│ userData │
│ folder │
└─────────────┘
Main Process Setup
// main.ts
import { app, ipcMain, BrowserWindow } from 'electron';
import { AgentPool, JSONStore, Agent } from '@shareai-lab/kode-sdk';
import * as path from 'path';
let mainWindow: BrowserWindow;
let pool: AgentPool;
let store: JSONStore;
async function initializeAgent() {
store = new JSONStore(path.join(app.getPath('userData'), 'agents'));
pool = new AgentPool({
dependencies: {
store,
templateRegistry: /* ... */,
sandboxFactory: /* ... */,
toolRegistry: /* ... */,
},
maxAgents: 10,
});
}
// IPC: Send message to agent
ipcMain.handle('agent:send', async (event, { agentId, message }) => {
let agent = pool.get(agentId);
if (!agent) {
const exists = await store.exists(agentId);
agent = exists
? await pool.resume(agentId, { templateId: 'desktop-assistant' })
: await pool.create(agentId, { templateId: 'desktop-assistant' });
}
return agent.complete(message); // complete() handles send + wait
});
// IPC: Subscribe to events (streaming)
ipcMain.on('agent:subscribe', (event, { agentId }) => {
const agent = pool.get(agentId);
if (!agent) return;
// Stream events to renderer
(async () => {
for await (const env of agent.subscribe(['progress'])) {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send(`agent:event:${agentId}`, env.event);
}
if (env.event.type === 'done') break;
}
})();
});
// IPC: Create new agent
ipcMain.handle('agent:create', async (event, { agentId, templateId }) => {
const agent = await pool.create(agentId, { templateId });
return { agentId: agent.agentId, status: 'created' };
});
// IPC: List agents
ipcMain.handle('agent:list', async () => {
return store.list();
});
// IPC: Delete agent
ipcMain.handle('agent:delete', async (event, { agentId }) => {
await pool.delete(agentId); // pool.delete also removes from store
return { success: true };
});
// IPC: Handle permission requests
ipcMain.on('agent:permission-subscribe', (event, { agentId }) => {
const agent = pool.get(agentId);
if (!agent) return;
agent.on('permission_required', async (permEvent) => {
mainWindow.webContents.send(`agent:permission:${agentId}`, {
callId: permEvent.call.id,
toolName: permEvent.call.name,
input: permEvent.call.inputPreview,
});
});
});
ipcMain.handle('agent:permission-respond', async (event, { agentId, callId, decision, note }) => {
const agent = pool.get(agentId);
if (!agent) return { error: 'Agent not found' };
await agent.decide(callId, decision, note);
return { success: true };
});
app.whenReady().then(async () => {
await initializeAgent();
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
},
});
mainWindow.loadFile('index.html');
});
// Graceful shutdown
app.on('before-quit', async () => {
for (const agentId of pool.list()) {
const agent = pool.get(agentId);
if (agent) await agent.interrupt();
}
});
Preload Script
// preload.ts
import { contextBridge, ipcRenderer } from 'electron';
contextBridge.exposeInMainWorld('agent', {
send: (agentId: string, message: string) =>
ipcRenderer.invoke('agent:send', { agentId, message }),
create: (agentId: string, templateId: string) =>
ipcRenderer.invoke('agent:create', { agentId, templateId }),
list: () => ipcRenderer.invoke('agent:list'),
delete: (agentId: string) =>
ipcRenderer.invoke('agent:delete', { agentId }),
subscribe: (agentId: string, callback: (event: any) => void) => {
ipcRenderer.send('agent:subscribe', { agentId });
ipcRenderer.on(`agent:event:${agentId}`, (_, event) => callback(event));
},
subscribePermission: (agentId: string, callback: (req: any) => void) => {
ipcRenderer.send('agent:permission-subscribe', { agentId });
ipcRenderer.on(`agent:permission:${agentId}`, (_, req) => callback(req));
},
respondPermission: (agentId: string, callId: string, decision: 'allow' | 'deny', note?: string) =>
ipcRenderer.invoke('agent:permission-respond', { agentId, callId, decision, note }),
});
Renderer (React)
// App.tsx
import React, { useState, useEffect, useRef } from 'react';
declare global {
interface Window {
agent: {
send: (agentId: string, message: string) => Promise<any>;
create: (agentId: string, templateId: string) => Promise<any>;
list: () => Promise<string[]>;
subscribe: (agentId: string, callback: (event: any) => void) => void;
subscribePermission: (agentId: string, callback: (req: any) => void) => void;
respondPermission: (agentId: string, callId: string, decision: 'allow' | 'deny', note?: string) => Promise<any>;
};
}
}
function App() {
const [agentId] = useState('main-agent');
const [messages, setMessages] = useState<{ role: string; content: string }[]>([]);
const [input, setInput] = useState('');
const [streaming, setStreaming] = useState('');
const [pendingApproval, setPendingApproval] = useState<any>(null);
useEffect(() => {
// Subscribe to agent events
window.agent.subscribe(agentId, (event) => {
switch (event.type) {
case 'text_chunk':
setStreaming(prev => prev + event.delta);
break;
case 'done':
setMessages(prev => [...prev, { role: 'assistant', content: streaming }]);
setStreaming('');
break;
}
});
// Subscribe to permission requests
window.agent.subscribePermission(agentId, (req) => {
setPendingApproval(req);
});
}, [agentId]);
const handleSend = async () => {
if (!input.trim()) return;
setMessages(prev => [...prev, { role: 'user', content: input }]);
setInput('');
await window.agent.send(agentId, input);
};
const handleApproval = async (decision: 'allow' | 'deny') => {
if (!pendingApproval) return;
await window.agent.respondPermission(agentId, pendingApproval.callId, decision);
setPendingApproval(null);
};
return (
<div className="app">
<div className="messages">
{messages.map((msg, i) => (
<div key={i} className={`message ${msg.role}`}>
{msg.content}
</div>
))}
{streaming && <div className="message assistant streaming">{streaming}</div>}
</div>
{pendingApproval && (
<div className="approval-dialog">
<p>Tool requires approval: {pendingApproval.toolName}</p>
<pre>{JSON.stringify(pendingApproval.input, null, 2)}</pre>
<button onClick={() => handleApproval('allow')}>Allow</button>
<button onClick={() => handleApproval('deny')}>Deny</button>
</div>
)}
<div className="input-area">
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleSend()}
placeholder="Type a message..."
/>
<button onClick={handleSend}>Send</button>
</div>
</div>
);
}
export default App;
Best Practices for Desktop Apps
- Run KODE SDK in Main Process - Renderer should only handle UI
- Use IPC for Communication - Never expose Node.js APIs directly to renderer
- Graceful Shutdown - Interrupt agents before app quit
- Store in userData - Use
app.getPath('userData')for persistence - Stream Events - Don't batch events, stream them for responsive UI
- Handle Permissions - Show approval dialogs for sensitive tools
See also: Production Deployment | Architecture Guide