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───────────────────────────────────▶│

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

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}")

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:

  1. Allowlist enforcement — the sandbox rejects writes outside sandbox_allowlist (verified post-run by git diff --name-only).
  2. 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