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.
Prerequisites
Section titled “Prerequisites”- A Claude subscription, and the Claude Code CLI on a machine logged into it.
- A PromptPack for the agent’s
promptPackRef.
Create the credential
Section titled “Create the credential”On the machine logged into the subscription, create a long-lived token:
claude setup-tokenStore it in a Secret in the agent’s namespace:
kubectl create secret generic claude-subscription --from-literal=token=<token>Deploy the agent
Section titled “Deploy the agent”apiVersion: omnia.altairalabs.ai/v1alpha1kind: AgentRuntimemetadata: name: claude-codespec: 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.
Give the agent tools
Section titled “Give the agent tools”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.
Configure the prompt and skills
Section titled “Configure the prompt and skills”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.
Answer the agent’s questions
Section titled “Answer the agent’s questions”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.
Limit concurrent sessions
Section titled “Limit concurrent sessions”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.
Keep conversations across pod restarts
Section titled “Keep conversations across pod restarts”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.
Verify
Section titled “Verify”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.
Compare usage
Section titled “Compare usage”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.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause |
|---|---|
Runtime container exits with cannot create /home/claude/work or HOME ... is not writable | The 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 Y | The 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 restart | The agent has no Redis context store. |
A reply fails with every Claude Code slot is busy | Every slot was mid-turn until the deadline. Raise OMNIA_CLI_MAX_CONCURRENT or reduce the load. |
| The agent says it has no tools | The 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 by | A 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.