README
¶
Agentic Control gives host applications one app-owned way to observe and
control multiple agent runtimes without baking vendor-specific behaviour into
the product layer. Use agent_control when your application needs to start,
resume, interrupt, and answer runtime requests directly. Use agent_harness
when you need passive hook, plugin, or extension observation for unmanaged
sessions,
investigation, or diagnostics.
This repository is designed around three outcomes:
- Keep runtime instrumentation generic enough to port between applications.
- Keep correlation explicit so the helper never assumes your internal role model or environment variable names.
- Keep setup practical with one shared installer, a shared helper binary, and a live debug mode that mirrors the production transport.
Choose your integration path first:
agent_controlUse this when your application owns the session lifecycle and needs a unified control plane across Codex, Gemini, Claude, OpenCode, and pi.agent_harnessUse this when the runtime is launched elsewhere and you only need passive hook, plugin, or extension events translated into the shared event contract.
If you are integrating this into a host product, start with:
docs/control-plane.mdfor app-managed sessionsdocs/integration.mdfor the host integration model- the runtime guide for the runtime you are wiring in first
The public surface stays intentionally small. The shared helper binary
starts at cmd/agent-harness/main.go, the
runtime translation logic lives in internal/harness/,
and the shared contracts live in pkg/contract/. The
provider-facing Go boundary for the controller lives in
pkg/controlplane/. The Go agent_harness binary
owns event translation, hook, plugin, and extension bundle installation,
and interactive live-run diagnostics. runtimes/ contains the
runtime-specific fixtures, prompts, plugin source, and notes. docs/
holds the durable contract and integration guidance. mise.toml
and hk.pkl are the source of truth for local automation, and
scripts/ holds only the README asset generator and README
validator.
Host applications should prefer the Go SDK helpers in pkg/contract and
pkg/controlplane over matching event strings or reimplementing session
adoption locally. The package exposes event constants, request-event and
terminal-turn helpers, payload extraction, turn accumulation, and
adopt-or-resume session helpers so applications can stay focused on their own
workflow model.
The repository has two runtime surfaces:
agent_harnessfor passive hook, plugin, and extension observationagent_controlfor app-owned Codex, Gemini, Claude, OpenCode, and pi sessions
agent_control exposes a single bootstrap call, system.describe, so host
applications can discover runtime capabilities before they start or resume
sessions.
agent_control is the primary integration surface for new application
work. agent_harness is the secondary path for passive observation,
investigation, unmanaged external sessions, and native hook, plugin, or
extension capture.
The repository stays generic on purpose. It does not know about your internal roles, workflow states, or ownership model. Instead, your application chooses what to bind and what to infer. The helper only translates runtime-native signals into a stable, app-owned event stream.
Runtime coverage:
- Codex via native hooks and
codex app-server - Gemini via native hooks and
gemini --acp - Claude via native hooks and a local Claude Agent SDK bridge
- OpenCode via native plugins and
opencode serve - pi via native extensions and
pi --mode rpc
If you want a quick local evaluation, build the binaries and replay the sample fixtures first:
mise trust
mise install
mise run build
mise run diag:fixtures:codex
mise run diag:fixtures:gemini
mise run diag:fixtures:claude
mise run diag:fixtures:opencode
mise run diag:fixtures:pi
The helper builds with Go. You do not need a Zig toolchain to replay
fixtures or install the runtime bundles. The build also bootstraps the Claude
Agent SDK bridge dependency the first time you compile agent_control.
The most useful local commands are:
| Command | Purpose |
|---|---|
mise run build |
Build agent_harness and agent_control. |
mise run diag:listen |
Start a local debug listener on the default socket. |
mise run control:serve |
Start the Go control-plane on a local Unix socket. |
mise run diag:fixtures:codex |
Replay every Codex fixture through the helper. |
mise run diag:fixtures:gemini |
Replay every Gemini fixture through the helper. |
mise run diag:fixtures:claude |
Replay every Claude fixture through the helper. |
mise run diag:fixtures:opencode |
Replay every OpenCode fixture through the helper. |
mise run diag:fixtures:pi |
Replay every pi fixture through the helper. |
mise run diag:install:codex |
Install the repo-local Codex bundle for live testing. |
mise run diag:install:gemini |
Install the repo-local Gemini bundle for live testing. |
mise run diag:install:claude |
Install the repo-local Claude bundle for live testing. |
mise run diag:install:opencode |
Install the global OpenCode bundle for live testing. |
mise run diag:install:pi |
Install the repo-local pi bundle for live testing. |
mise run diag:codex:smoke |
Run a live Codex smoke scenario. |
mise run diag:codex:bash |
Run a live Codex Bash scenario. |
mise run diag:codex:approval |
Run a live Codex approval scenario. |
mise run diag:gemini:smoke |
Run a live Gemini smoke scenario. |
mise run diag:gemini:bash |
Run a live Gemini Bash scenario. |
mise run diag:gemini:approval |
Run a live Gemini approval scenario. |
mise run diag:claude:smoke |
Run a live Claude smoke scenario. |
mise run diag:claude:bash |
Run a live Claude Bash scenario. |
mise run diag:claude:approval |
Run a live Claude approval scenario. |
mise run diag:opencode:smoke |
Run a live OpenCode smoke scenario. |
mise run diag:opencode:bash |
Run a live OpenCode Bash scenario. |
mise run diag:opencode:approval |
Run a live OpenCode approval scenario. |
mise run diag:pi:smoke |
Run a pi smoke scenario. |
mise run diag:pi:bash |
Run a pi tool scenario. |
mise run diag:pi:approval |
Run a pi write scenario. |
For direct helper usage without mise, run:
.artifacts/bin/agent_harness listen --socket-path /tmp/agent-harness.sock
.artifacts/bin/agent_harness --runtime codex --stdout < runtimes/codex/fixtures/session_start.json
.artifacts/bin/agent_harness install --runtime codex --scope repo --socket-env AGENT_HARNESS_SOCKET
.artifacts/bin/agent_harness install --runtime pi --scope repo --socket-env AGENT_HARNESS_SOCKET
.artifacts/bin/agent_harness uninstall --runtime codex --scope repo
.artifacts/bin/agent_harness uninstall --runtime pi --scope repo
.artifacts/bin/agent_control serve --socket-path /tmp/agentic-control.sock
.artifacts/bin/agent_control describe --socket-path /tmp/agentic-control.sock
Each runtime bundle is kept independent so that the shared contract does not need to change when a vendor changes its hook surface.
Installation is centralised. Use the shared Go installer with --runtime and,
when needed, --scope:
.artifacts/bin/agent_harness install --runtime codex --scope repo --socket-env AGENT_HARNESS_SOCKET
.artifacts/bin/agent_harness install --runtime gemini --scope repo --socket-env AGENT_HARNESS_SOCKET
.artifacts/bin/agent_harness install --runtime claude --scope repo --socket-env AGENT_HARNESS_SOCKET
.artifacts/bin/agent_harness install --runtime opencode --scope global --socket-env AGENT_HARNESS_SOCKET
.artifacts/bin/agent_harness install --runtime pi --scope repo --socket-env AGENT_HARNESS_SOCKET
To safely remove only the Agentic Control hook, plugin, or extension content later, use the matching uninstall command:
.artifacts/bin/agent_harness uninstall --runtime codex --scope repo
.artifacts/bin/agent_harness uninstall --runtime gemini --scope repo
.artifacts/bin/agent_harness uninstall --runtime claude --scope repo
.artifacts/bin/agent_harness uninstall --runtime opencode --scope global
.artifacts/bin/agent_harness uninstall --runtime pi --scope repo
The hook bundles and live scenarios in this repository were validated on April 5, 2026 against these installed CLI versions:
| Runtime | Validated version | Install guide | Native reference |
|---|---|---|---|
| Codex | codex-cli 0.118.0 |
Codex CLI quickstart and install | Codex hooks |
| Gemini | 0.36.0 |
Gemini CLI installation | Gemini CLI hooks reference |
| Claude | 2.1.84 (Claude Code) |
Claude Code setup | Claude Code hooks reference |
| OpenCode | 1.3.15 |
OpenCode install guide | OpenCode plugins |
| pi | 0.66.1 |
pi package install | pi RPC mode |
If you are running different versions, rerun the fixture replay and live diagnostic tasks before assuming the same hook payloads or launch behaviour.
Runtime guides:
- Codex runtime guide
- Gemini runtime guide
- Claude runtime guide
- OpenCode runtime guide
- pi runtime guide
- Control-plane guide
Official reference links:
- Codex hooks
- Codex plugins
- Codex plugin packaging
- Gemini CLI hooks guide
- Gemini CLI hooks reference
- Claude Code hooks reference
- OpenCode install guide
- OpenCode plugins
- OpenCode config
- OpenCode permissions
- OpenCode server
- pi package install
- pi RPC mode
- pi extensions
The repository uses the strongest native extension surface each runtime exposes. That means hooks where a runtime provides hooks, and plugins where a runtime exposes plugin-native lifecycle events. The event contract stays built around hook-like investigation signals rather than indirect tool shims.
Repo-local installation is the default for Codex, Gemini, Claude, and pi.
OpenCode is global by default because it already auto-loads plugins from a dedicated
global plugin directory without editing opencode.json. Every runtime
supports an explicit repo or global install mode where that distinction is
useful. When you install both a global and a repo-local OpenCode bundle, the
repo-local bundle is the active bundle for that repository, and the global
plugin does not emit duplicate events.
Install and uninstall are intentionally runtime-local. The Go installer keeps each bundle under the runtime’s own repo-local or global config tree so it can remove only the Agentic Control content later without guessing about shared state.
For host applications, that means you can adopt the runtime bundle that matches your immediate need without leaking app-specific naming into the shared helper. Your application decides what to bind. The helper only standardises the native payload shape and transport.
The normalised event contract is documented in
docs/contract.md, and the machine-readable Go contract
types live in pkg/contract/. At a high level, every event
contains:
- runtime identity
- native event name
- normalised event type
- a concise summary
- runtime-native session or tool identifiers when available
- optional
bindingscontributed by the host application
The normalised event families are:
session.startedsession.endedturn.user_prompt_submittedturn.finishedturn.failedturn.stoppedtool.startedtool.finishedtool.failedtool.permission_requestednotificationruntime.event
The helper preserves native identifiers where the runtime exposes them. That is
why the contract includes fields such as session_id, turn_id,
tool_call_id, tool_name, command, cwd, model, transcript_path, and
runtime_pid when they are available.
If you want the runtime-specific details behind those fields, read the dedicated reference pages:
- Codex runtime guide
- Gemini runtime guide
- Claude runtime guide
- OpenCode runtime guide
- pi runtime guide
The session and event types used by the Go control-plane also live in
pkg/contract/controlplane.go.
Bindings are the mechanism that keeps the helper portable. Instead of assuming that every application exports the same environment variables, the helper lets you opt into correlation explicitly.
If your application exports:
APP_LAUNCH_IDAPP_SESSION_IDAPP_ACTOR_IDAPP_HOST_ID
you can pass:
--bind-env launch_id=APP_LAUNCH_ID \
--bind-env app_session_id=APP_SESSION_ID \
--bind-env actor_id=APP_ACTOR_ID \
--bind-env host_id=APP_HOST_ID
You can also attach fixed values:
--bind-value environment=staging
If you do not pass any --bind-env or --bind-value flags, the helper emits
no application-level bindings at all.
This is the key portability rule for the repository: the runtime layer speaks a generic contract, and your application decides what each binding means.
The runtime flow is deliberately simple:
- A runtime hook, plugin, or extension fires.
- The runtime invokes
agent_harness. - The helper reads the incoming payload, maps it to the shared contract, and appends any requested bindings.
- The helper writes the event to a local receiver or to
stdout. - Your application consumes that stream and derives higher-level state.
That design keeps the helper as a deep module with a simple interface. The runtime-specific translation logic lives in one place, while application logic stays outside the repository.
The repository supports two operating modes.
Product mode is the real integration path. Your application launches the
runtime, injects any binding environment variables it wants to expose, and
points the helper at a local receiver through --socket-path or
--socket-env.
Debug mode is intentionally explicit. It is there to help you verify what a runtime is sending and how the helper is translating it. The simplest path is:
mise run diag:listen
In another terminal, run a live scenario:
mise run diag:codex:smoke
mise run diag:gemini:bash
mise run diag:claude:approval
The Go agent_harness run command starts a listener, sets
AGENT_HARNESS_SOCKET, launches the runtime in an approval-capable mode, and
prints live [hook] lines as events arrive.
This repository deliberately keeps approval-sensitive scenarios in place. The live diagnostic paths are not YOLO-mode shortcuts because the goal is to verify actual runtime behaviour, including approval gates.
The README presentation layer is generated and checked from source. That keeps the documentation maintainable rather than turning it into a one-off manual layout exercise.
The main support tooling is:
scripts/generate_assets.pygenerates the banner and section headers used by the README.scripts/validate_readme.pyverifies that the README references the expected assets and local paths.cmd/agent-harness/main.goalso exposes the Goinstall,uninstall, andrunsubcommands used for live runtime diagnostics and hook, plugin, or extension bundle management.assets/architecture.d2is the source of truth for the sequence diagram embedded above asassets/architecture.svg.
The generated assets live in assets/. When you change the README
structure or visual language, regenerate the assets and rerun the README
validator.
mise run generate-assets
mise run validate-docs
The repository is split into a small number of clear modules:
| Path | Purpose |
|---|---|
cmd/ |
Binary entrypoints for the shared helper and future control-plane tools. |
internal/harness/ |
Shared hook, plugin, and extension translation logic. |
internal/controlplane/ |
Active-session registry, event bus, server, and provider implementations. |
pkg/contract/ |
Shared Go contract types for harness and control-plane work. |
pkg/controlplane/ |
Provider-native control-plane interfaces and request types. |
runtimes/ |
Runtime-specific fixtures, prompts, plugin source, and notes. |
docs/ |
Durable contract and integration documentation. |
scripts/ |
Asset generation and README validation. |
assets/ |
Generated README visuals. |
mise.toml |
Local automation entrypoint. |
hk.pkl |
hk hook and verification configuration. |
Next steps
If you want to integrate this into another application, start with
docs/integration.md, install the runtime bundle you
need, and decide what your host application wants to bind as launch_id,
app_session_id, actor_id, and host_id.