Skip to content

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, and SessionEnd.
  • 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.

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.

References