A Shareable Agent Harness Across Coding CLIs
This page examines whether a Python service built with the OpenAI Agents SDK can be shared by Codex CLI, Claude Code, OpenCode, and Gemini CLI instead of bundling the entire workflow into a skill or a container.
The short answer is yes, but the service should be the shared core, not a replacement for every client integration:
client skill or command
-> thin client adapter
-> shared Python harness service
-> model, memory, policy, and observability integrations
Skills, hooks, and context files remain client-specific. The service boundary should carry a small, explicit task envelope rather than assuming that one CLI can see another CLI's conversation.
Can Codex CLI call a Python Agents SDK subagent?
Yes, in two different senses.
Shell invocation
Codex can run a Python script as a shell command when its permissions and sandbox allow it. That script is an external process, not a native Codex subagent. It receives no automatic copy of the current Codex conversation.
The script must be given the context it needs:
Codex session:
run("python harness.py", input = task_envelope)
Python harness:
read(task_envelope)
run OpenAI Agents SDK workflow
return structured result
This is simple, but the shell boundary makes context, permissions, errors, and process lifetime the caller's responsibility.
Codex as an MCP worker
The more capable pattern is the reverse: a Python Agents SDK process is the orchestrator and launches Codex CLI as an MCP server:
Python orchestrator:
start "codex mcp-server"
register the MCP server with an OpenAI Agent
send a scoped coding task to the Codex MCP tool
receive a result and thread_id
use codex-reply(thread_id, next_prompt) when continuation is needed
The official Codex MCP server exposes codex for starting a conversation and
codex-reply for continuing a thread. The thread state belongs to Codex; it is
not automatically merged into the Python orchestrator's conversation. The
orchestrator must explicitly pass summaries, artifacts, or the returned
thread identifier between stages.
The OpenAI Agents SDK also supports native agent handoffs. A handoff normally passes the conversation history to the receiving agent, with filters available when only part of the history should be transferred. This is different from the Codex MCP thread, which is a separate context boundary.
What should be shared?
Share the harness core, not the entire client surface:
shared Python package/service
- task envelope schema
- collection and redaction
- category labeling
- distillation
- approval state
- memory and evidence storage
- patch or PR generation
- telemetry and evaluation
client adapters
- discoverability
- current session metadata
- local file and tool events
- authentication and permissions
- client-specific user interaction
A portable task envelope might contain:
TaskEnvelope:
task_id
client
repository
worktree
objective
relevant_files
diff_reference
decisions
constraints
artifacts
requested_action
privacy_policy
Do not send the full transcript by default. Send only the minimum context needed for the next operation, and store larger artifacts by reference. This reduces token cost and limits accidental transfer of credentials or personal data.
Context sharing is not automatic
Each client has its own conversation, compaction behavior, permissions, and session identifiers. A shared service can provide a common context store, but the adapter still has to publish and retrieve context:
on client_event(event):
normalized = normalize_client_event(event)
harness.append_event(normalized)
before_next_step:
context = harness.build_context(
task_id = current_task,
include = ["decisions", "artifacts", "open_questions"]
)
send_to_client(context)
The service should distinguish:
- Conversation context: transient turns owned by one client session.
- Task state: durable progress needed to resume or hand off work.
- Project memory: approved facts and rules shared by the repository.
- Observability data: traces and metrics that should not be treated as instructions.
This prevents a Langfuse trace, an unreviewed transcript, or a client-specific prompt from becoming an accidental project rule.
Skills sharing
Skills are good for client-facing instructions, but they are not a portable runtime ABI. Each client discovers and loads them differently:
| Client | Shareable skill or extension surface |
|---|---|
| Codex CLI | Skills and plugins; plugins can bundle skills and optional MCP connections |
| Claude Code | Skills, subagents, rules, and plugins |
| OpenCode | .opencode/skills/, plugins, commands, and project configuration |
| Gemini CLI | Extensions that can bundle skills, prompts, commands, MCP, hooks, and subagents |
A cross-client skill should therefore be a thin wrapper:
skill "review-memory":
explain when to call the shared harness
collect the current client task envelope
call harness.review(...)
present the returned proposal or approval request
The real logic stays in the Python service. This avoids copying the same distillation prompts, schemas, redaction rules, and policy decisions into four different Markdown formats.
The tradeoff is that each client still needs a small native wrapper. One
unmodified SKILL.md cannot reliably provide identical discovery, permissions,
tool calls, or interaction behavior across all four clients.
Hook sharing
Hooks are better for deterministic client-side events than skills:
session start
prompt submitted
before tool
after tool
context compaction
session end
The event names and I/O contracts differ:
- Claude Code hooks can be command, HTTP, MCP-tool, prompt, or agent hooks. Command, HTTP, and MCP-tool hooks are deterministic.
- OpenCode exposes JavaScript/TypeScript plugins with event hooks such as
session.idle,tool.execute.before,tool.execute.after, and file events. - Gemini CLI hooks are synchronous programs using JSON over stdin/stdout,
with events including
SessionStart,BeforeAgent,BeforeTool,AfterTool,PreCompress, andSessionEnd. - Codex can package optional lifecycle hooks in plugins, but its exact hook surface and configuration should be checked against the installed release. Its stable cross-process integration point is MCP.
The shared hook should be a small executable or HTTP call:
client_native_hook(event):
event = read_client_event()
normalized = normalize(event)
POST harness/events(normalized)
return client_specific_allow_or_continue_response()
The normalization layer is essential. A Gemini hook's JSON response contract cannot be copied directly into a Claude or OpenCode plugin.
Centralized statistics and Langfuse
A shared harness is a useful place to centralize statistics, but it should not be the only place that can observe client behavior.
client hook telemetry
-> normalized event collector
-> OpenTelemetry spans and metrics
-> Langfuse or another OTLP backend
Python Agents SDK
-> agent, tool, handoff, and model traces
-> OpenTelemetry/Langfuse
Codex MCP worker
-> separate Codex-side trace boundary
-> correlate using task_id and thread_id
OpenAI Agents SDK tracing can cover agent steps, tools, handoffs, and model
operations. Langfuse provides an OpenAI Agents integration, and OpenTelemetry
can serve as the transport boundary. For other clients, native hooks can
capture session and tool events, but they may not expose the same model-token
details. Use a shared task_id, session_id, client, and thread_id where
available to correlate partial traces.
Do not log raw prompts, tool arguments, file contents, or transcripts by default. Make trace-content capture an explicit privacy setting, redact secrets before export, and provide a local-only or disabled telemetry mode.
Compare the packaging approaches
| Approach | Strength | Weakness | Best use |
|---|---|---|---|
| Client skill only | Very easy to install and discover | Duplicated logic, weak state, client-specific behavior | Small one-client workflow |
| Skill plus scripts | Simple, inspectable, works offline | Dependency drift and weak concurrent state | Personal or small-team prototype |
| Docker container | Reproducible dependencies and isolation | Image lifecycle, filesystem/auth complexity, heavier local UX | Fixed build or sandbox environment |
| Versioned Python package/CLI | Easy to pin, test, and run locally | Each client still needs a wrapper | Small team sharing one harness |
| Local Python service | Shared state and one implementation | Process lifecycle and local authentication | Multiple clients on one workstation |
| HTTP/MCP harness service | Cross-language and cross-client boundary | Deployment, auth, tenancy, latency, privacy | Team or organization-wide service |
| Full agent framework | Durable orchestration, retries, traces, handoffs | More abstractions and operational overhead | Long-running, concurrent workflows |
For this research use case, a versioned Python package plus an optional local MCP/HTTP server is usually the best middle ground. A container can package the service later when reproducibility or sandboxing matters; it should not be required merely to share prompts and schemas.
Recommended progression
Stage 1: shared library
Publish a normal Python package containing schemas and pure workflow functions. Each client calls a local command or imports a thin adapter:
python -m harness label --input item.json
python -m harness distill --batch labeled/
python -m harness approve --proposal proposal.json
Use SQLite or files for state. This is easy to inspect and works without a service.
Stage 2: local service
Add an MCP or HTTP server around the same package:
MCP tool "harness_label":
validate TaskEnvelope
label evidence
return structured result
MCP tool "harness_distill":
retrieve labeled evidence
generate a proposal
return proposal_id
MCP tool "harness_approve":
require human decision
persist approval
create a patch or PR request
The client skills now describe how and when to call these tools. The service owns schemas, policy, storage, and telemetry.
Stage 3: shared team service
Add authentication, repository and user scopes, retention, queues, retries, concurrency control, and an audit log. Keep approved project instructions in Git even when the service stores searchable evidence.
Final judgment
Sharing a Python/OpenAI Agents SDK harness is more predictable than bundling the entire workflow into a skill because the core code has one dependency environment, one test suite, one schema, and one observability path. It is more portable than a container because clients invoke a stable command or protocol instead of needing to understand a full image runtime.
It is not magic cross-client interoperability. The team still needs thin adapters for skills, hooks, permissions, context extraction, and user interaction. The most robust boundary is:
native client wrapper
-> versioned task envelope
-> shared Python harness
-> explicit result, artifact, or approval request
Use skills for discoverability, hooks for deterministic interception and telemetry, MCP/HTTP for the shared service boundary, and Git for approved project knowledge.