NIB agent runner — headless Claude Code / Codex container that executes plan steps in a repo
1.7K
Headless coding-agent runner for NIB (Neuro Infrastructure Builder). NIB plans infrastructure work down to elementary steps; when a step is a code change, the backend launches this image — one container per step on the host Docker daemon, or one Kubernetes Job — and it does the work in a real checkout of a real repository.
The container is a complete unit of work. It clones the repo, resolves the MCP configuration and system prompt, runs the agent non-interactively against the prompt, commits and pushes a branch, opens a pull request, and reports the outcome back to NIB over a webhook. Nothing to babysit and nothing left running afterwards.
One image, two runtimes. AGENT_TYPE selects Claude Code (default) or OpenAI Codex, and
the job contract — repository, prompt, MCP servers, tool permissions, git identity, webhook — is the
same for both. Switching the agent behind a step is an environment variable, not a redeploy.
It is one of four images: nib-agent (this one), nib-backend
(API and agent orchestrator), nib-frontend (web UI)
and nib-kb (knowledge-base MCP server).
CLAUDE_CODE_OAUTH_TOKEN (Anthropic subscription — the container
then clears the gateway defaults and talks to api.anthropic.com) or ANTHROPIC_API_KEY against
ANTHROPIC_BASE_URL, which defaults to OpenRouter. Codex takes OPENAI_API_KEY against
OPENAI_BASE_URL (OpenRouter's Responses API by default).REPO_URL set: clone BASE_BRANCH (or detect the remote default branch)
into /workspace/repo, install a git credential helper backed by GITHUB_TOKEN, then either
check out an existing remote TARGET_BRANCH or create it from the base. Re-running a step
against a branch that already exists continues that branch rather than colliding with it.
Without REPO_URL it works in /workspace/scratch — MCP-only mode, for steps that drive
external systems instead of editing code.MCP_CONFIG), as a file (MCP_CONFIG_FILE) or raw
(MCP_CONFIG_RAW); ${NAME} placeholders in it are resolved at this point, so tokens never sit
in the config that was written to disk. MCP_ENABLED / MCP_DISABLED gate which servers and
tools survive. By default (REQUIRE_MCP=1) a server that fails to connect fails the run —
an agent that quietly proceeded without the tool it needed to check something is worse than one
that stopped.--output-format stream-json --verbose, so every turn is
parseable rather than a wall of text; Codex runs via codex exec. TIMEOUT_SECONDS wraps
either one in a hard deadline.git add -A, commit, push TARGET_BRANCH, then reuse
an existing PR for that head branch or create one with gh. No changes means no branch and no
PR — a step that decided nothing needed doing does not leave litter behind.WEBHOOK_URL (3 retries, 30 s cap), including the tail of the log
so a failure is diagnosable from the NIB chat without going to look at container logs.Settings → Executor in the NIB UI:
/var/run/docker.sock into the backend and set DOCKER_HOST.javdet/nib-agent:<tag> and Agent Secret Name to an
existing Secret in the target namespace holding the agent credentials; it is mounted with
envFrom. The Job is rendered from backend/internal/executor/templates/job.yaml.tmpl, which you
can override by dropping your own at {DATA_DIR}/job.yaml.tmpl.Standalone, it is just a container you hand a prompt and a repository:
docker run --rm \
-e PROMPT="Bump the ingress-nginx chart to 4.11.3 and update the values comment" \
-e REPO_URL=https://github.com/org/infra.git \
-e BASE_BRANCH=main \
-e TARGET_BRANCH=nib/ingress-bump \
-e PR_TITLE="Bump ingress-nginx to 4.11.3" \
-e GITHUB_TOKEN="$GH_TOKEN" \
-e ANTHROPIC_API_KEY="$OPENROUTER_KEY" \
-e GIT_AUTHOR_NAME="nib-agent" -e GIT_AUTHOR_EMAIL="[email protected]" \
-e TIMEOUT_SECONDS=1800 \
javdet/nib-agent:latest
| Variable | Default | Purpose |
|---|---|---|
AGENT_TYPE | claude-code | claude-code (aliases claude) or codex |
PROMPT | — | The task. Required |
SYSTEM_PROMPT_FILE | — | System prompt file; a relative path resolves inside the workspace |
SYSTEM_PROMPT_MODE | append | append to the agent's own system prompt, or replace it |
TIMEOUT_SECONDS | unset | Hard deadline for the agent run |
| Variable | Purpose |
|---|---|
REPO_URL | Repository to clone. Omit for MCP-only mode in /workspace/scratch |
BASE_BRANCH | Branch to clone and to target with the PR. Auto-detected from the remote if unset |
TARGET_BRANCH | Branch to create or continue and push |
GITHUB_TOKEN | Clone, push and gh pr create credential |
PR_TITLE | PR title and commit message. Defaults to the first line of PROMPT |
PR_BODY | PR body. Defaults to the whole PROMPT |
GIT_AUTHOR_NAME / GIT_AUTHOR_EMAIL | Commit identity |
| Variable | Default | Purpose |
|---|---|---|
ANTHROPIC_API_KEY | — | Claude Code key; mapped to ANTHROPIC_AUTH_TOKEN at start |
CLAUDE_CODE_OAUTH_TOKEN | — | Anthropic subscription auth; overrides the gateway defaults |
ANTHROPIC_BASE_URL | https://openrouter.ai/api | No /v1 suffix — Claude Code appends /v1/messages itself |
ANTHROPIC_MODEL | anthropic/claude-opus-5 | Model slug |
OPENAI_API_KEY | — | Codex key |
OPENAI_BASE_URL | https://openrouter.ai/api/v1 | Responses API base |
OPENAI_MODEL | openai/gpt-5.3-codex | Model slug |
| Variable | Default | Purpose |
|---|---|---|
MCP_CONFIG / MCP_CONFIG_FILE / MCP_CONFIG_RAW | — | MCP server definitions, with ${NAME} placeholders resolved at launch |
MCP_ENABLED / MCP_DISABLED | — | Per-server and per-tool gating |
REQUIRE_MCP | 1 | Fail the run if any configured MCP server does not connect. 0 to tolerate |
ALLOWED_TOOLS / DISALLOWED_TOOLS | — | Comma-separated tool allow/deny lists |
PERMISSION_MODE | acceptEdits (Codex mapping) | Claude Code --permission-mode; also drives the Codex approval/sandbox mapping |
CODEX_SANDBOX_MODE | — | Codex sandbox level |
CODEX_NETWORK_ACCESS | 1 | Network access inside the Codex sandbox |
CODEX_CONFIG_TOML / CODEX_CONFIG_FILE_PATH / CODEX_EXTRA_ARGS | — | Extra Codex configuration |
DEBUG_MCP | — | Verbose MCP resolution logging |
| Variable | Purpose |
|---|---|
WEBHOOK_URL | Where to POST the result — NIB's /api/v1/agent-runner/webhook. Skipped if unset |
WEBHOOK_AUTH_HEADER | Full header line, e.g. Authorization: Bearer <AGENT_WEBHOOK_TOKEN> |
TASK_ID / CHAT_ID / THREAD_ROOT_ID | Correlation ids echoed back so NIB can route the result to the right chat |
JOB_NAME / POD_NAME / POD_NAMESPACE | Kubernetes identity for the report |
WEBHOOK_RESULT_MAX_BYTES / WEBHOOK_LOG_TAIL_MAX_BYTES | 16384 each — truncation caps |
{
"status": "succeeded",
"exit_code": 0,
"agent_type": "claude-code",
"result": "…final assistant message, truncated to WEBHOOK_RESULT_MAX_BYTES…",
"log_tail": "…tail of the run log…",
"task_id": "…", "thread_root_id": "…", "chat_id": "…",
"job": { "name": "…", "pod": "…", "namespace": "…", "image_version": "…" },
"repo": { "url": "…", "base_branch": "main", "target_branch": "nib/…",
"pushed": true, "pr_url": "https://github.com/…/pull/42" },
"usage": { "duration_ms": 184000, "num_turns": 12, "total_cost_usd": 0.41,
"input_tokens": 0, "output_tokens": 0, "session_id": "…" }
}
If the full payload cannot be built, a minimal one with status: "failed" is sent instead — the
backend always hears something back, rather than waiting on a job that silently died. On the NIB
side these land in the nib_agent_runner_* metrics (results, turns, duration, cost, webhook
outcomes).
@anthropic-ai/claude-code and the codex CLI, both pinned at build timegh), git, jq, less, gettext-base, bubblewrap (Codex sandboxing)agent-entrypoint.sh — the whole flow above, roughly 1000 lines of deliberately boring bashnode:24-trixie-slim; runs as the unprivileged node user with /workspace as the working
directoryA note on privileges: this container checks out your code and runs an agent with tool access inside
it. Give it a scoped GITHUB_TOKEN (one repository, no admin), keep it off the host network, and do
not mount the Docker socket into it — the backend, not the agent, is what needs the socket.
latest · vX.Y.Z · <git-sha> (exact commit). Built for linux/amd64 and linux/arm64.
This image is rebuilt only when agent-runner/universal-agent/** changes, so its version tags can
lag behind the other NIB images — that is expected, not a broken build. It has no schema or API
coupling to a specific backend release; pin <git-sha> or vX.Y.Z and upgrade it on its own
schedule.
agent-runner/universal-agent/agent-entrypoint.shLICENSE before deploying this in a commercial setting.Content type
Image
Digest
sha256:9e6faff63…
Size
325.5 MB
Last updated
15 days ago
docker pull javdet/nib-agent