Sign inSign up

javdet/nib-agent

By javdet

•Updated 15 days ago

NIB agent runner — headless Claude Code / Codex container that executes plan steps in a repo

Image
Integration & delivery
Machine learning & AI
Developer tools
0

1.7K

javdet/nib-agent repository overview

⁠NIB — agent runner

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).


⁠What one run does

  1. Auth. Claude Code takes 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).
  2. Workspace. With 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.
  3. MCP. The server list arrives inline (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.
  4. Run. Claude Code executes with --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.
  5. Deliver. If the working tree changed: 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.
  6. Report. POST the result to 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.

⁠Using it from NIB

Settings → Executor in the NIB UI:

  • Local (Docker socket) — the backend runs this image on the host daemon. Mount /var/run/docker.sock into the backend and set DOCKER_HOST.
  • Remote → Kubernetes — set Image to 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

⁠Job contract

⁠Agent selection and task
VariableDefaultPurpose
AGENT_TYPEclaude-codeclaude-code (aliases claude) or codex
PROMPT—The task. Required
SYSTEM_PROMPT_FILE—System prompt file; a relative path resolves inside the workspace
SYSTEM_PROMPT_MODEappendappend to the agent's own system prompt, or replace it
TIMEOUT_SECONDSunsetHard deadline for the agent run
⁠Repository and delivery
VariablePurpose
REPO_URLRepository to clone. Omit for MCP-only mode in /workspace/scratch
BASE_BRANCHBranch to clone and to target with the PR. Auto-detected from the remote if unset
TARGET_BRANCHBranch to create or continue and push
GITHUB_TOKENClone, push and gh pr create credential
PR_TITLEPR title and commit message. Defaults to the first line of PROMPT
PR_BODYPR body. Defaults to the whole PROMPT
GIT_AUTHOR_NAME / GIT_AUTHOR_EMAILCommit identity
⁠Model and auth
VariableDefaultPurpose
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_URLhttps://openrouter.ai/apiNo /v1 suffix — Claude Code appends /v1/messages itself
ANTHROPIC_MODELanthropic/claude-opus-5Model slug
OPENAI_API_KEY—Codex key
OPENAI_BASE_URLhttps://openrouter.ai/api/v1Responses API base
OPENAI_MODELopenai/gpt-5.3-codexModel slug
⁠Tools, MCP and sandboxing
VariableDefaultPurpose
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_MCP1Fail the run if any configured MCP server does not connect. 0 to tolerate
ALLOWED_TOOLS / DISALLOWED_TOOLS—Comma-separated tool allow/deny lists
PERMISSION_MODEacceptEdits (Codex mapping)Claude Code --permission-mode; also drives the Codex approval/sandbox mapping
CODEX_SANDBOX_MODE—Codex sandbox level
CODEX_NETWORK_ACCESS1Network access inside the Codex sandbox
CODEX_CONFIG_TOML / CODEX_CONFIG_FILE_PATH / CODEX_EXTRA_ARGS—Extra Codex configuration
DEBUG_MCP—Verbose MCP resolution logging
⁠Callback
VariablePurpose
WEBHOOK_URLWhere to POST the result — NIB's /api/v1/agent-runner/webhook. Skipped if unset
WEBHOOK_AUTH_HEADERFull header line, e.g. Authorization: Bearer <AGENT_WEBHOOK_TOKEN>
TASK_ID / CHAT_ID / THREAD_ROOT_IDCorrelation ids echoed back so NIB can route the result to the right chat
JOB_NAME / POD_NAME / POD_NAMESPACEKubernetes identity for the report
WEBHOOK_RESULT_MAX_BYTES / WEBHOOK_LOG_TAIL_MAX_BYTES16384 each — truncation caps

⁠Webhook payload

{
  "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).


⁠Image contents

  • @anthropic-ai/claude-code and the codex CLI, both pinned at build time
  • GitHub CLI (gh), git, jq, less, gettext-base, bubblewrap (Codex sandboxing)
  • agent-entrypoint.sh — the whole flow above, roughly 1000 lines of deliberately boring bash
  • Base node:24-trixie-slim; runs as the unprivileged node user with /workspace as the working directory

A 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.


⁠Tags and platforms

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.


  • Source, documentation and issues: https://github.com/javdet/nib⁠
  • Entrypoint source: agent-runner/universal-agent/agent-entrypoint.sh
  • Licence: Nib Non-Commercial License 1.0 (MIT-based, with attribution and a non-commercial-use restriction). Read LICENSE before deploying this in a commercial setting.

Tag summary

Content type

Image

Digest

sha256:9e6faff63…

Size

325.5 MB

Last updated

15 days ago

docker pull javdet/nib-agent