Orchestrata Integration — Kimi, Claude Code, and agentic harnesses
Principle: Orchestrata should orchestrate agentic harnesses, not re-implement them. Claude Code, Kimi Code, and OpenCode already do tool use, file editing, search, bash, and self-correction well. Orchestrata's durable value is routing, governance (Prolog), memory (knowledge graph), sessions, and observation (the Redis queue stream). Integration means volks hire harnesses under governance instead of being raw LLM calls.
This page is the implementation-level companion to CLI-PROPOSAL.
1. Architecture
┌──────────────────────────────────────────┐
│ USER │
│ orchestrata intent / respond / watch │
└─────────────────┬────────────────────────┘
│ WebSocket JSON-RPC
┌─────────────────▼────────────────────────┐
│ GATEWAY (ws :8765) │
│ auth · RPC table · pub/sub · ring buf │
└─────────────────┬────────────────────────┘
│ gRPC :50053
┌─────────────────▼────────────────────────┐
│ KERNEL / INTENT BROKER │
│ classify → route → VolkDriver.select() │
└───────┬─────────────────────┬────────────┘
│ │
┌─────────────────▼──────┐ ┌──────────▼─────────────┐
│ PROLOG GOVERNANCE │ │ VOLK DRIVERS │
│ may_execute (pre) │ │ ┌──────────────────┐ │
│ may_complete (post) │ │ │ InProcessLLM │ │
└────────────────────────┘ │ │ HeadlessHarness │ │
│ │ RemoteFleet(IRC) │ │
│ └──────────────────┘ │
└──────────┬─────────────┘
│
┌───────────────────┬─────────────────────────┼────────────────────┐
▼ ▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌─────────────┐ ┌──────────┐
│ Claude │ │ Kimi Code │ │ OpenCode │ │ IRC fleet│
│ Code -p │ │ --print │ │ sandboxed │ │ members │
└───────────┘ └───────────┘ └─────────────┘ └──────────┘
│ │ │ │
└───────────────────┴─────────┬───────────────┴────────────────────┘
▼
┌──────────────────────────────────┐
│ REDIS QUEUE — versioned events │◀── Prolog verdicts
│ session_id · volk · stage · ts │◀── harness events
└──────────────────────────────────┘
▲
┌─────────────────┴─────────────────┐
│ KNOWLEDGE GRAPH (memgraph) │
│ Project→Session→Intent→Artifact │
└───────────────────────────────────┘
The Redis queue is the spine. Every backend emits the same event shape;
every surface (CLI watch, VizInt, TUI, IRC) renders that one stream.
2. The VolkDriver interface
One seam in the kernel: VolkDriver.execute(packet, context) -> ExecutionResult.
Three backends. Selection is per-volk config in orchestrata.yaml.
# src/orchestrata_ultimate/kernel/drivers/base.py
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, Protocol
from orchestrata_ultimate.cxp.packet import CXPPacket
@dataclass
class ExecutionContext:
"""Everything a driver needs that the kernel owns."""
session_id: str # server-generated, embedded in the session object
project_root: str # workspace folder (worktrees resolved to parent)
volk_alias: str # "U:Ar", "U:Qa", ...
sandbox_allowlist: list[str] # files the agent may modify
emit_event: Any # callable(stage, payload) -> None (Redis queue)
@dataclass
class ExecutionResult:
status: str # "resolved" | "needs_work" | "blocked"
summary: str
artifacts: list[str] = field(default_factory=list) # repo-relative paths
open_questions: list[str] = field(default_factory=list)
class VolkDriver(Protocol):
name: str
async def execute(
self, packet: CXPPacket, context: ExecutionContext
) -> ExecutionResult:
"""Run the intent, streaming progress via context.emit_event."""
...
Backend 1 — InProcessLLM (current behavior, kept)
Cheap volks and degraded mode. What volks do today: one provider call, no tools. Unchanged code path.
Backend 2 — HeadlessHarnessDriver (the integration core)
Spawns a real agentic CLI in a sandboxed subprocess, feeds it the intent + context pack, parses its events back onto the queue.
# src/orchestrata_ultimate/kernel/drivers/headless.py
from __future__ import annotations
import asyncio
import json
import os
from pathlib import Path
from orchestrata_ultimate.kernel.drivers.base import (
ExecutionContext, ExecutionResult,
)
HARNESS_COMMANDS = {
# intent text is appended after the flags; keep invocations non-interactive
"claude": ["claude", "-p", "--output-format", "stream-json"],
"kimi": ["kimi", "--print", "--yolo"],
"opencode": ["opencode", "run", "--json"],
}
class HeadlessHarnessDriver:
name = "headless"
def __init__(self, harness: str = "claude") -> None:
if harness not in HARNESS_COMMANDS:
raise ValueError(f"unknown harness: {harness}")
self._harness = harness
async def execute(self, packet, context: ExecutionContext) -> ExecutionResult:
prompt = _build_prompt(packet, context)
env = _sandbox_env(context)
cmd = [*HARNESS_COMMANDS[self._harness], prompt]
context.emit_event("dispatched", {
"harness": self._harness, "volk": context.volk_alias,
})
proc = await asyncio.create_subprocess_exec(
*cmd,
cwd=context.project_root,
env=env,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
start_new_session=True, # survives, killable as a group
)
try:
async for event in _stream_events(proc.stdout, self._harness):
# normalize harness-native events onto OUR queue schema
context.emit_event(event["stage"], event["payload"])
rc = await asyncio.wait_for(proc.wait(), timeout=1800)
except asyncio.TimeoutError:
proc.kill()
return ExecutionResult(
status="blocked", summary="harness timed out (1800s)"
)
if rc != 0:
stderr = (await proc.stderr.read()).decode()[-2000:]
return ExecutionResult(
status="blocked", summary=f"harness exited {rc}: {stderr}"
)
return ExecutionResult(
status="resolved",
summary=f"{self._harness} completed",
artifacts=_collect_artifacts(context),
)
def _sandbox_env(context: ExecutionContext) -> dict:
"""Minimal env for the child: no PYTHONPATH (false-green trap), scoped
file allowlist passed through for harnesses that support it."""
env = {
"PATH": os.environ.get("PATH", ""),
"HOME": os.environ.get("HOME", ""),
"ORCHESTRATA_SESSION_ID": context.session_id,
"ORCHESTRATA_VOLK": context.volk_alias,
"ORCHESTRATA_ALLOWLIST": ":".join(context.sandbox_allowlist),
}
return env
Backend 3 — RemoteFleetDriver (existing IRC pattern, formalized)
The fleet already does this informally (kimi: TASK <desc> in <workdir> on
#agents, daemon acks and posts results). Formalize: the driver publishes a
task envelope on the Redis queue with a correlation session id; the remote
member's daemon executes and publishes completion events back. Same event
schema, so watch cannot tell local from remote.
class RemoteFleetDriver:
name = "fleet"
async def execute(self, packet, context) -> ExecutionResult:
envelope = {
"type": "fleet.task",
"session_id": context.session_id,
"volk": context.volk_alias,
"target_nick": self._target, # "kimi", "claude", "codewhale"
"workdir": context.project_root,
"description": packet.payload.get("text", ""),
}
await self._queue.publish("fleet.tasks", envelope)
context.emit_event("dispatched", {"harness": f"irc:{self._target}"})
# completion arrives asynchronously on fleet.results.<session_id>;
# the kernel's reply consumer resolves the pending ExecutionResult
return await self._await_result(context.session_id, timeout=3600)
3. Governance wraps the harness (unchanged semantics)
Dispatch sequence — Prolog gates before and after, exactly as today:
kernel prolog driver queue
│ intent │ │ │
│──may_execute(V,A)────▶│ │ │
│◀──allowed/denied──────│ │ │
│ (denied) ───────────────────────────────────────▶ governance.blocked
│ (allowed) ──execute(packet, ctx)─────────────▶ │ │
│ │ │──stream events────▶│
│ │ │──result───────────▶│
│──may_complete(V,R)──▶│ │ │
│◀──gates pass/fail────│ │ │
│ (fail) ──needs_work + reasons───────────────────────────────────▶│
│ (pass) ──work_status=resolved───────────────────────────────────▶│
may_execute/2evaluates the intent before any harness is spawned — a denied intent costs zero subprocesses.may_complete/2(REQ-000-087..093) evaluates the result: all acceptance criteria met, outputs verified, no open questions, downstream not blocked. A harness that declares victory without artifacts fails the gate and getsneeds_workwith the Prolog reasons.
4. One versioned event schema
Everything — kernel lifecycle, harness stdout, Prolog verdicts, fleet replies — normalizes to this envelope before touching Redis:
{
"schema": "orchestrata.event/1",
"ts": "2026-07-28T17:05:11Z",
"session_id": "ses_9f2c",
"project": "auth-service",
"volk": "U:Ar",
"driver": "headless:claude",
"stage": "design",
"kind": "stage_enter",
"payload": {"summary": "proposing 3-layer architecture"}
}
Reserved kind values: stage_enter, stage_exit, question (waiting on
human), answer, proposal, verdict, artifact, blocked, resolved,
governance.blocked. Version field lets the watcher evolve without breaking
VizInt/TUI/IRC consumers mid-flight.
Harness → schema normalizer (per harness, ~50 lines each):
def normalize_claude_event(raw: dict, ctx) -> dict:
"""Claude stream-json -> orchestrata.event/1."""
kind_map = {
"assistant": "stage_progress",
"tool_use": "artifact",
"result": "resolved",
}
return {
"schema": "orchestrata.event/1",
"session_id": ctx.session_id,
"volk": ctx.volk_alias,
"driver": "headless:claude",
"kind": kind_map.get(raw.get("type"), "stage_progress"),
"stage": raw.get("subtype", "working"),
"payload": raw.get("message", raw),
}
5. Session and project binding
- Sessions are server-side objects (
session.openRPC); the session id is minted there and embedded — the CLI never asks the user for it (CLI-PROPOSAL v4). ExecutionContext.project_rootcomes from cwd → project root resolution; worktrees resolve to the parent project by parsing the.gitfile'sgitdir:pointer:
def resolve_project_root(cwd: Path) -> Path:
"""Walk up to the workspace project folder; map worktrees to parents."""
for candidate in (cwd, *cwd.parents):
git = candidate / ".git"
if git.is_file(): # worktree: .git is a FILE
gitdir = git.read_text().split("gitdir:", 1)[1].strip()
# .../main-checkout/.git/worktrees/<name> -> main-checkout
return Path(gitdir).resolve().parents[1]
if git.is_dir():
return candidate
raise LookupError(f"no project root above {cwd}")
- The graph hierarchy stores it all:
(Project)-[:HAS_SESSION]->(Session)-[:HAS_INTENT]->(Intent) -[:PRODUCED]->(Artifact), with every queue event carryingsession_idso the stream and the graph join trivially.
6. Sandboxing (reuse scripts/orchestrator)
The existing scripts/orchestrator/ already runs sandboxed agents with
designated-file allowlists and AST-injected context. The harness driver
adopts its two hard rules:
- Allowlist enforcement — the sandbox rejects writes outside
sandbox_allowlist(verified post-run bygit diff --name-only). - Context injection — the prompt embeds the AST slice of the allowlisted
files (
serialize_codebase_ast.py symbols), not the whole repo.
7. Rollout plan
| Phase | Deliverable | Proof |
|---|---|---|
| 0 | orchestrata.event/1 schema + Redis publisher in kernel |
stream visible in watch for in-process volks |
| 1 | VolkDriver interface + InProcessLLM adapter (refactor only) |
zero behavior change, suite green |
| 2 | HeadlessHarnessDriver (claude) + one volk (U:Qa) wired | end-to-end: intent → claude -p → artifacts → may_complete |
| 3 | Kimi + OpenCode normalizers; per-volk driver config | same intent run on 3 harnesses, identical stream shape |
| 4 | RemoteFleetDriver formalized (IRC envelope) | fleet.tasks round-trip through #agents daemon |
| 5 | Session-bound CLI (CLI-PROPOSAL v4) on top | intent/respond/review/approve/watch with no IDs |
8. Trade-offs and risks
- Cost/latency: harness subprocesses are heavier than API calls — keep InProcessLLM for classification/heartbeat volks; harnesses for build volks.
- Nondeterminism: harnesses explore. The sandbox allowlist + post-run
diff check is the containment;
may_completeis the quality gate. - Secret hygiene: children get a minimal env (no PYTHONPATH, no credentials beyond what the harness itself needs to auth to its provider).
- Timeouts/zombies: 1800s cap,
start_new_session=True, kill-process- group on timeout — the daemon SIGTERM lessons apply here too.
Links
- CLI-PROPOSAL — the session/project CLI model
- SPEC-000 (orchestrata-cli), SPEC-001 (kernel), SPEC-003 (intent), SPEC-005 (multi-agent), SPEC-007 (prolog-governance), SPEC-020 (claude-code-harness), SPEC-021 (opencode-integration)
scripts/orchestrator/— existing sandboxed dispatch pipeline