Skip to content

Run a Claude Code agent

Run an agent whose runtime container drives the Claude Code CLI and bills inference to one person’s Claude subscription instead of per token. Every session the agent serves draws on that subscription, so run one such agent per person.

The facade, sessions and exposure work as for any agent. The CLI’s file, shell and web builtins are off, so nothing runs on the runtime pod. The agent’s tools are its ToolRegistry tools.

  • A Claude subscription, and the Claude Code CLI on a machine logged into it.
  • A PromptPack for the agent’s promptPackRef.

On the machine logged into the subscription, create a long-lived token:

Terminal window
claude setup-token

Store it in a Secret in the agent’s namespace:

Terminal window
kubectl create secret generic claude-subscription --from-literal=token=<token>
apiVersion: omnia.altairalabs.ai/v1alpha1
kind: AgentRuntime
metadata:
name: claude-code
spec:
promptPackRef: { name: claude-code-pack }
framework:
type: custom
image: ghcr.io/altairalabs/omnia-claude-runtime:main
facades:
- type: websocket
runtime:
replicas: 1
extraEnv:
- name: CLAUDE_CODE_OAUTH_TOKEN
valueFrom:
secretKeyRef: { name: claude-subscription, key: token }
volumes:
- name: cli-home
emptyDir: {}
volumeMounts:
- { name: cli-home, mountPath: /home/claude }

Put the credential and the volume under spec.runtime, which applies to the runtime container only. podOverrides also applies to the facade container and would expose the token to it.

Keep replicas: 1 and leave autoscaling off. A session’s CLI process lives in the pod that started it, and every session shares one subscription.

The cli-home volume is required. The runtime container’s root filesystem is read-only, and the CLI keeps its own state under /home/claude. Without the volume the runtime exits at startup with an error that names spec.runtime.volumes.

To choose the model, add OMNIA_CLI_MODEL to extraEnv (for example sonnet or haiku). Unset uses the CLI’s default.

Reference a ToolRegistry, exactly as for a PromptKit agent:

spec:
toolRegistryRef: { name: claude-code-tools }

The model sees each tool as mcp__omnia__<tool>. Each call goes through the same executor as a PromptKit agent’s, so ToolPolicy decisions, injected headers, credentials and delegation apply to it. Calls appear on the session’s tool calls in the dashboard.

For a workspace to work in, give the ToolRegistry a sandbox and a handler whose sandbox block names it, and enable spec.sandbox on the AgentRuntime. Each session gets its own sandbox, claimed on its first sandbox call, or when the session starts if the sandbox sets allocation: eager. See Codegen sandbox for how a sandbox is isolated and reclaimed.

The CLI keeps two builtins of its own. Task runs a sub-agent with the same tools, and Skill runs a skill.

The agent’s PromptPack configures it as it does any agent. The runtime appends the pack’s prompt to the CLI’s own system prompt, and the pack’s skills are available to the CLI’s Skill tool. A changed pack takes effect when the agent’s pods roll out. Live and compile-mode packs are not supported.

When the agent needs a decision, it asks. In the dashboard console the question appears as a card: pick an option, tick several for a multi-select question, or type your own answer under Other, then choose Answer. Skip tells the agent you declined.

Over A2A, from a function call or on a schedule there is nobody to ask. The agent is told so, proceeds on its best judgment and states the assumption it made. The session’s tool calls show each unanswered question.

Every session draws on the one subscription’s usage allowance. The runtime runs at most four CLI processes at once, across all sessions and function invocations. To change the limit, set OMNIA_CLI_MAX_CONCURRENT in extraEnv.

When every slot is held, the runtime stops the least recently used idle session’s process to make room. That session resumes its conversation on its next message. When every slot is mid-turn, a new message waits for one. If its deadline passes first, it fails with ResourceExhausted.

omnia_cli_runtime_gate_waits_total counts messages that had to wait. A rising count means the limit, or the subscription, is too small for the load.

The runtime stops a session’s CLI process after 30 minutes idle and resumes the conversation on the next message. By default it keeps what it resumes from in memory, so a session resumes while the pod lives.

To resume after the pod is replaced too, give the agent a Redis context store. storeRef names a Secret in the agent’s namespace with a url key holding the Redis URL:

spec:
context:
type: redis
storeRef: { name: redis-context }

The runtime then stores each session’s conversation in Redis for 24 hours. To change the lifetime, set spec.context.ttl to a duration such as 48h.

Terminal window
kubectl get agentruntime claude-code -o jsonpath='{.status.runtimeCapabilities}'

The output is ["invoke","client_tools"]. The runtime sets the field once, after it starts serving. Open the agent in the dashboard console and send a message; the reply streams as it is written.

The runtime records token counts on each message with a cost of 0, because nothing is billed per token.

Each turn is also recorded as a provider call with the source claude-code-notional, carrying the CLI’s token counts and what they would have cost at API list price. Use it to compare agents or to find the session that used the subscription’s allowance. Cost totals leave it out, and token totals include it.

SymptomCause
Runtime container exits with cannot create /home/claude/work or HOME ... is not writableThe cli-home volume is missing from spec.runtime, or is not writable.
Runtime container exits with claude CLI is X but this runtime was built against YThe image’s CLI was replaced. Use the published image.
A reply fails with claude CLI turn failed (api status 401 ...)The token is invalid or expired. Create a new one with claude setup-token.
A conversation restarts from scratch after a pod restartThe agent has no Redis context store.
A reply fails with every Claude Code slot is busyEvery slot was mid-turn until the deadline. Raise OMNIA_CLI_MAX_CONCURRENT or reduce the load.
The agent says it has no toolsThe runtime logs tool bridge init failed at startup when the ToolRegistry’s tools could not be loaded. Check the ToolRegistry’s status and the handler endpoints.
A tool call fails with denied byA ToolPolicy denied it. The session’s tool calls show the rule under denied_by.

See Authoring a custom runtime for the runtime contract this image implements.