@typooo/kekkai
v0.0.1
Published
A unified execution facade for AI Agent CLIs
Maintainers
Readme
Kekkai — The casting platform for agents
Kekkai (結界) is a unified execution facade for AI Agent CLIs — structured events, session management, and pluggable backends.
Core Capabilities
- Unified Execution API:
Kekkai.run(id, request)/stream(request)— abstracts CLI protocol differences - Pluggable Backends: Claude, Codex, OpenCode, Antigravity, Raw — auto-register, zero core changes to add one
- Structured Runtime Events: 12 event types with schema version, sequence, and timestamp metadata
- NDJSON Logging: One JSON line per event, with redaction, filtering, and fan-out
- Session Management: In-memory or file-based, with key pinning and resume
Quick Start
import { Kekkai } from 'kekkai';
// 1. Create an agent instance with configuration
Kekkai.create('my-agent', {
defaultBackend: 'raw',
backends: {
raw: { command: 'echo', model: 'claude-sonnet-4' },
},
systemPrompt: 'You are a helpful assistant.',
permissions: { profile: 'edit' },
timeouts: { wall: 600000, idle: 180000, tool: 60000 },
});
// 2. Stream events (command is inherited from create-time config)
for await (const event of Kekkai.get('my-agent').stream({
agentId: 'my-agent',
prompt: 'hello world',
})) {
console.log(event.type, event);
}
// Or run directly (collects all events and returns a result)
const result = await Kekkai.run('my-agent', {
agentId: 'my-agent',
prompt: 'hello world',
});Built-in backends auto-register — no manual backendRegistry.register() needed.
Structured Output
Pass a JSON Schema to get validated, structured results:
const result = await Kekkai.run('my-agent', {
agentId: 'my-agent',
prompt: 'List 3 cat breeds with their temperaments',
schema: { // ← JSON Schema for structured output
type: 'object',
required: ['breeds'],
properties: {
breeds: {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string' },
temperament: { type: 'string' },
},
},
},
},
},
});
// result.structured → { breeds: [{ name: 'Siamese', temperament: '...' }, ...] }For Claude and Codex, the schema is passed natively via --structured-output. For other backends, it's appended to the prompt. The run() method validates the output and populates result.structured on success.
Architecture
┌─────────────────────────────────────────────────┐
│ Kekkai │
│ static: create / get / run / stream │
│ instance: run(request) → stream(request) │
│ → resolve backend → execute → yield events │
├─────────────────────────────────────────────────┤
│ Backend Registry LogSinks SessionStore │
│ Claude / Codex / Raw ConfigLoader │
├─────────────────────────────────────────────────┤
│ Agent CLI child processes │
└─────────────────────────────────────────────────┘Configuration
KekkaiConfig (create-time)
interface KekkaiConfig {
defaultBackend?: string;
systemPrompt?: string;
permissions?: { profile?: PermissionProfile };
timeouts?: { wall?: number; idle?: number; tool?: number };
backends?: Record<string, BackendConfig>;
}RunRequest (per-call)
| Field | Description |
|-------|-------------|
| agentId | Agent identifier |
| backend | Backend name (overrides defaultBackend) |
| command | Executable (overrides BackendConfig.command) |
| model | Model name (overrides BackendConfig.model) |
| prompt | User prompt |
| schema | JSON Schema — validates output, populates result.structured |
| cwd | Working directory |
| sessionKey / resumeSessionId | Session resume |
| signal | Cancellation signal |
| overrides | Partial AgentRuntimeConfig deep merge |
Precedence: RunRequest > BackendConfig > backend defaults
Backend Support
| Backend | Protocol | Registry Name |
|---------|----------|---------------|
| Claude | stream-json stdout (native --structured-output) | claude |
| Codex | JSON-RPC over stdio (native --structured-output) | codex |
| OpenCode | --format json events (configurable via format option) | opencode |
| Antigravity | stdio MCP config | antigravity |
| Raw | Arbitrary stdio command | raw |
Select by name:
// Configure backend (command and model are set at create-time)
Kekkai.create('my-agent', {
defaultBackend: 'claude',
backends: {
claude: { command: 'claude', model: 'claude-sonnet-4' },
},
});
// Run — no need to repeat command or model
const result = await Kekkai.run('my-agent', {
agentId: 'my-agent',
prompt: 'Explain this project architecture',
overrides: {
mcp: { mcpServers: { filesystem: { command: 'mcp-server-fs', args: ['--root', '/tmp'] } } },
},
});Implementing a Backend
Extend BaseBackend and implement two methods:
import { BaseBackend, SpawnParams } from 'kekkai';
class MyBackend extends BaseBackend {
readonly backend = 'my-backend';
protected buildArgs(input: BackendRunInput): SpawnParams {
return { command: 'my-cli', args: ['--flag'], stdinData: input.prompt };
}
protected parseLine(line: string, runId: string, agentId: string): RuntimeEvent | null {
return { type: 'message', role: 'assistant', content: line, /* ... */ } as RuntimeEvent;
}
// Optional: emit events before spawning
protected getPreludeEvents?(input: BackendRunInput, params: SpawnParams): RuntimeEvent[];
}BaseBackend.execute() handles the full pipeline: spawn → write stdin → read stdout/stderr → yield events → detect cancellation → yield run_end.
Or implement the full AgentBackend interface:
interface AgentBackend {
readonly backend: string;
execute(input: BackendRunInput): AsyncIterable<RuntimeEvent>;
cancel?(runId: string, reason?: string): Promise<void>;
}Reference: RawBackend (~50 lines) is the minimal implementation.
Registration
Built-in backends auto-register. Custom backends:
import { backendRegistry } from 'kekkai';
backendRegistry.register('my-backend', () => new MyBackend());Event Protocol
Events follow this sequence:
run_start (first)
├─ message / thinking / tool_use / tool_result / status / usage
├─ error (optional)
└─ stdout / stderr (optional)
run_end (last)Every event includes schemaVersion, runId, seq, timestamp, agentId, backend, and type.
| Type | Description |
|------|-------------|
| run_start | Execution started |
| message | Structured message (role: assistant/user/system) |
| thinking | Reasoning process |
| tool_use / tool_result | Tool invocation and result |
| stdout / stderr | Streaming text |
| status | Status update (includes sessionId) |
| usage | Token usage |
| audit | Security audit event |
| error | Error information |
| run_end | Execution finished |
Logging
import { FileLogSink, FanoutLogSink, MemoryLogSink, CallbackLogSink } from 'kekkai';
const fanout = new FanoutLogSink([
{ sink: new FileLogSink({ dir: './logs' }), filter: (e) => e.type !== 'thinking' },
{ sink: new MemoryLogSink() },
{ sink: new CallbackLogSink({ callback: (e) => ws.send(JSON.stringify(e)) }) },
]);CLI Usage
kekkai -c support.json "Analyze this ticket"
kekkai -c dev.json --json
kekkai -c review.json -b claude < prompt.txtOptions: -c (config path, required), -b (backend), --json (JSON output), -h (help).
Config files are JSON profiles — the filename (minus extension) is agentId:
{
"defaultBackend": "claude",
"systemPrompt": "You are a developer.",
"permissions": { "profile": "edit" },
"timeouts": { "wall": 600000, "idle": 1800000, "tool": 7200000 },
"backends": {
"claude": { "command": "claude", "model": "claude-sonnet-4", "args": ["--max-turns", "60"] },
"codex": { "command": "codex", "model": "claude-sonnet-4" },
"opencode": { "command": "opencode", "model": "claude-sonnet-4", "format": "json" }
}
}API Overview
| Export | Description |
|--------|-------------|
| Kekkai | Global lifecycle management (create/get/run/stream/remove/shutdown) |
| BaseBackend | Abstract base class (buildArgs + parseLine pattern) |
| RawBackend / ClaudeBackend / CodexBackend / OpenCodeBackend / AntigravityBackend | Backend implementations |
| MemoryLogSink / FileLogSink / FanoutLogSink / CallbackLogSink / NoopLogSink | Log writers |
| MemorySessionStore / FileSessionStore | Session persistence |
| JsonConfigLoader | JSON config loader |
| resolveConfig / validateConfig / defaultConfig | Config helpers |
| resolvePermissionProfile / registerPolicyMapper / getPolicyMapper | Permission mapping |
| validateSchema | JSON Schema validation for structured output |
| ResultParser | Interface for parsing output from events |
| serializeEvent / parseNDJSON | NDJSON serialization |
| redactEnv / redactString / isSensitiveKey | Log redaction |
| buildClaudeArgs / mapClaudeEvent / isTerminalEvent | Claude utilities |
Testing
npm test # Run all tests
npm run test:watch # Watch mode
npm run build # TypeScript compilationLicense
MIT
