The Agent Without a Face

There are two ways to use a coding agent: sit in its terminal, or take its face off and drive the engine from your own code. If you're building anything around agents, the second mode is the whole game — and it's full of traps nobody warns you about.

The Agent Without a Face — AI

Every coding agent ships with a face: the terminal UI you type into, watch scroll, and click allow in. Run claude or codex with no arguments and you're living inside their REPL. That face is great for using an agent. It is useless if you want to build on one.

Headless execution lets application code start an agent, consume structured events and apply its own orchestration. The important design choices are session continuity, authorization, error handling and independent verification of the result.

Normal mode versus headless

Interactive mode is claude or codex with no prompt: the rich TUI, live token counts, Shift+Tab to cycle permission modes, inline approve-and-remember. A human in the loop, steering in real time.

Headless execution uses claude -p "<prompt>" in Claude Code or codex exec "<prompt>" in Codex. A wrapper supplies the prompt and consumes the result without an interactive terminal. In unattended modes, approval-requiring operations may be denied. Handle permission-denial events separately from terminal run errors; behavior depends on the interface and configured permission policy. Your code owns that control flow.

A CLI and an SDK can expose the same underlying agent runtime through different interfaces. Inspect the provider’s supported package and version rather than assuming identical process architecture, memory use or callbacks across vendors.

Can you keep a session across calls? Yes — and here's the footgun

The first question every wrapper-builder asks: if I invoke the agent twice, does the second call remember the first? Yes. Both agents persist every session to disk and let you resume it.

# Claude Code — capture the session id, resume it on the next call
sid=$(claude -p "Start the refactor" --output-format json | jq -r '.session_id')
claude -p "Now wire up the tests" --resume "$sid"     # persisted session state restored; context may be compacted
#   --continue   resumes the most recent session in this dir
#   --fork-session  branches it into a new session id

# Codex — same idea, different verbs
codex exec "Start the refactor"
codex exec resume --last "Now wire up the tests" 

Resume restores persisted session state, which may include compacted context. It is not a guarantee that every prior read and tool result reappears verbatim. Keep durable task evidence and acceptance criteria available independently of the conversation.

Bind a wrapper to the intended working directory and explicit session identifier. Resume discovery varies by product and version; verify the selected session rather than silently continuing after a lookup failure.

Persisted history and active model context are different. Compaction can summarize earlier material while files remain on disk. Prefer supported event and session interfaces over parsing undocumented transcript formats.

How your app talks to it

Your wrapper needs two channels: events coming out, and decisions going in. Headless gives you both.

For Claude Code token-level streaming, the documented invocation combines --output-format stream-json, --verbose and --include-partial-messages. Parse the versioned event schema, including terminal errors, instead of assuming every line represents successful progress.

{"type":"system","subtype":"init","session_id":"...","model":"...","tools":[...]}   # grab session_id here
{"type":"assistant","message":{ ...text + tool_use blocks... }}
{"type":"stream_event","event":{"delta":{"type":"text_delta","text":"..."}}}   # token-by-token
{"type":"result","subtype":"success","total_cost_usd":0.04,"usage":{...}}      # final outcome

Claude and Codex expose provider-specific event schemas. Build a separate adapter for each, capture its documented session or thread identifier, and preserve errors and result metadata without pretending the vocabularies are identical.

Decisions in — and the approval interception. Single-shot headless is fire-and-forget. To drive a real multi-turn session — feed follow-ups, interrupt mid-task, answer a permission request — you switch to streaming input: --input-format stream-json, or in the SDK, pass an async generator of messages. That's what turns the agent from a one-shot command into a long-lived process your UI can hold a conversation with. And it's where a wrapper stops feeling like a log viewer and starts feeling native:

// Claude Agent SDK — your app becomes the permission dialog
for await (const msg of query({
  prompt: userTurns,                       // an async generator = a live, multi-turn session
  options: {
    canUseTool: async (tool, input) => {
      const ok = await myUI.ask(tool, input);          // render YOUR approval card
      if (ok) return { behavior: "allow", updatedInput: input };
      return { behavior: "deny", message: "User declined" };
    },
  },
})) renderEvent(msg);                       // stream events into your UI

The displayed example is a Claude Agent SDK callback shape. It is not the Codex SDK interface. Approval behavior depends on configured permissions and already-allowed tools; test allow, deny, cancellation and timeout paths. Do not rely on undocumented behavior in third-party applications.

The three embedding shapes

Strip it down and you're choosing between three ways to attach, and the choice is the architecture:

SurfaceTypical useContract to verify
CLI processScripts and CIArguments, exit status and structured event schema
Provider SDKApplication integrationProvider-specific session, cancellation and approval interfaces
App-server protocolRich stateful clientsVersioned requests, events and approval messages

Choose the integration surface by the control it supports: process lifecycle, events, session persistence, cancellation and approvals. Treat these as versioned contracts and test upgrades against the behaviors your wrapper depends on.

How people actually build this

Agent-management applications often coordinate workers, branches and event streams. Their authentication and isolation designs differ. A worktree separates files; it does not restrict a worker’s access to the host or external systems.

CLI, SDK and app-server integrations expose different control surfaces. Use supported native documentation for the selected provider and separate file-collision prevention from actual filesystem, credential and network restrictions.

The traps that aren't in the docs

Five design checks matter before embedding an agent:

Authentication: distinguish subscription login from API-key authentication and use the provider-authorized route for the product you are building. Attribution: inspect the effective identity and usage records; ChatGPT sign-in is not evidence that an API key was created. Results: combine process status and structured result/error events with independent acceptance checks. Storage: monitor persisted history and follow a deliberate retention policy that preserves required recovery evidence. Context: use bounded phases when useful, without claiming a universal degradation time.

Claude Code’s dontAsk permission mode denies operations that require approval; it does not grant blanket execution. Unattended operation needs a deliberate allow policy. Isolation depends on actual mounts, credentials, process privileges and network controls, not simply on calling a directory a sandbox.

“But isn't it just a wrapper?”

Build in this space and you'll hear it within a week, usually on Hacker News: “you're just a Claude Code wrapper — where's the moat?” It's the right question, and it has a real answer. The agent is the engine, and the engine is a commodity you bring your own of. The product is everything the engine doesn't give you: orchestration across many agents, the approval and review UX, durable session and state management, multi-agent coordination, the verification layer that decides when work is actually done, and — the part that compounds — the domain knowledge you wire in around it. The face you take off is generic. The one you put back on is the whole business.

Hot takes

Headless makes an agent controllable by software. Reliability comes from explicit state, correct provider adapters, scoped authority and verified acceptance. A successful invocation is not synonymous with a successfully completed task.

Sources: Claude Code programmatic usage; Claude Code authentication; Codex authentication; Codex non-interactive mode. Documentation checked September 20, 2026.