Skip to content

investigation-controller

Two engines in one binary. The gate admits findings that are Enhanced, have a closed accumulation window, and are older than --finding-min-age; admission materializes the Repository (if absent) and one immutable Investigation per attempt — the child create is the lease. The analysis scheduler then runs agent Jobs under bounded concurrency in severity order, parses the PATCHY-EVENT: stream, stamps the results, and routes the verdict onto the Finding.

investigation-controller serve --namespace patchy \
  --claude-agent-image ghcr.io/bitwise-media-group/patchy/claude-agent-runner:v0.9.0 # match your installed release

Pipeline flags

The shared flags, plus:

Flag Env Default Purpose
--finding-min-age PATCHY_FINDING_MIN_AGE 1h How old a finding must be before the gate admits it
--gate-concurrency PATCHY_GATE_CONCURRENCY 4 Findings admitted through the gate in parallel
--max-attempts PATCHY_MAX_ATTEMPTS 2 Analysis attempts per finding before it fails
--max-concurrent-investigations PATCHY_MAX_CONCURRENT_INVESTIGATIONS 3 Simultaneously running investigation Jobs
--confidence-threshold PATCHY_CONFIDENCE_THRESHOLD 0.75 Confidence required to queue automated remediation
--priority-aging-interval PATCHY_PRIORITY_AGING_INTERVAL 24h Wait per effective-priority point of aging boost
--priority-aging-cap PATCHY_PRIORITY_AGING_CAP 25 Maximum aging boost

Agent Job flags

Shared with the remediation-controller — both job controllers build Jobs the same way, from a per-harness runner fleet. A runner is configured when its image flag is set and enabled when it is also in --harnesses (or, when that is empty, its credential Secret exists). The controller resolves each stage's model to a harness and runs the matching runner. At startup it validates that every allowlisted model has an enabled harness that supports it, refusing to start otherwise.

Flag Env Default Purpose
--claude-agent-image PATCHY_CLAUDE_AGENT_IMAGE claude-agent-runner image (bundles the claude CLI); unset disables the claude runner
--codex-agent-image PATCHY_CODEX_AGENT_IMAGE codex-agent-runner image (bundles the codex CLI); unset disables the codex runner
--copilot-agent-image PATCHY_COPILOT_AGENT_IMAGE copilot-agent-runner image (bundles the copilot CLI); unset disables the copilot runner
--fake-agent-image PATCHY_FAKE_AGENT_IMAGE fake agent image for dev/e2e (replays fixtures, no credential)
--harnesses PATCHY_HARNESSES — (auto) Restrict enabled harnesses to this comma list; empty = any harness whose credential exists
--broker-url PATCHY_BROKER_URL The egress broker's base URL — required when a claude runner is configured (claude runs proxy-only)
--broker-token-audience PATCHY_BROKER_TOKEN_AUDIENCE patchy-egress-broker Audience the agent pods' projected broker caller tokens are bound to (must match the broker's --token-audience)
--claude-provider PATCHY_CLAUDE_PROVIDER anthropic Model provider behind the broker: anthropic, bedrock, vertex, or foundry
--claude-provider-region PATCHY_CLAUDE_PROVIDER_REGION Provider region (required for bedrock and vertex)
--claude-provider-region-prefix PATCHY_CLAUDE_PROVIDER_REGION_PREFIX Bedrock inference-profile prefix override when it cannot be derived from the region (us/eu/apac)
--claude-provider-project-id PATCHY_CLAUDE_PROVIDER_PROJECT_ID GCP project id (required for vertex)
--claude-model-map PATCHY_CLAUDE_MODEL_MAP canonical=provider-id overrides, comma separated — required for foundry, whose deployment names cannot be derived
--claude-provider-env PATCHY_CLAUDE_PROVIDER_ENV Extra NAME=value agent-pod env for the provider, comma separated (credential names rejected)
--claude-secret{,-key,-env} PATCHY_CLAUDE_SECRET{,_KEY,_ENV} (legacy) Ignored — claude is brokered and its pods hold no credential; customizing these logs a migration notice
--codex-secret PATCHY_CODEX_SECRET patchy-openai Secret (agent namespace) holding the OpenAI credential
--codex-secret-key PATCHY_CODEX_SECRET_KEY api-key Key within the OpenAI credential Secret
--codex-secret-env PATCHY_CODEX_SECRET_ENV OPENAI_API_KEY Env var it is injected as: OPENAI_API_KEY, or CODEX_API_KEY / CODEX_ACCESS_TOKEN for a ChatGPT-plan workspace token
--copilot-secret PATCHY_COPILOT_SECRET patchy-copilot Secret (agent namespace) holding the GitHub Copilot credential — a GitHub token, not a model API key
--copilot-secret-key PATCHY_COPILOT_SECRET_KEY token Key within the GitHub Copilot credential Secret
--copilot-secret-env PATCHY_COPILOT_SECRET_ENV COPILOT_GITHUB_TOKEN Env var it is injected as: COPILOT_GITHUB_TOKEN, GH_TOKEN, or GITHUB_TOKEN
--agent-namespace PATCHY_AGENT_NAMESPACE patchy-agents Namespace the agent Jobs run in
--agent-service-account PATCHY_AGENT_SERVICE_ACCOUNT patchy-agent ServiceAccount for the agent pods
--job-deadline PATCHY_JOB_DEADLINE 1h activeDeadlineSeconds for an agent Job
--job-ttl PATCHY_JOB_TTL 1h ttlSecondsAfterFinished for a finished Job
--model-allowlist PATCHY_MODEL_ALLOWLIST canonical ids Canonical model ids the investigation may request for remediation (comma-separated)

Stage flags

The investigation stage's limits are absolute — the stage runs on exactly these. Its model is a canonical, provider-qualified id (e.g. anthropic/claude-sonnet-5); the harness that runs it is derived from the model. The two remediate ceilings exist here because this controller clamps what the investigation report requests before it ever reaches the queue.

Flag Env Default Purpose
--investigate-model PATCHY_INVESTIGATE_MODEL anthropic/claude-sonnet-5 Canonical model id the analysis stage runs on
--investigate-timeout PATCHY_INVESTIGATE_TIMEOUT 15m Wall-clock limit for the analysis stage
--investigate-max-turns PATCHY_INVESTIGATE_MAX_TURNS 25 Agent turns allowed for the analysis stage
--investigate-token-budget PATCHY_INVESTIGATE_TOKEN_BUDGET 150000 Output-token budget for the analysis stage
--remediate-max-turns PATCHY_REMEDIATE_MAX_TURNS 80 Ceiling on the report's suggested remediation turns
--remediate-token-budget PATCHY_REMEDIATE_TOKEN_BUDGET 400000 Ceiling on the suggested remediation budget

The stage flags are re-serialized into PATCHY_* environment variables injected into every investigation pod (agent-runner reads them), so this controller's flags are the single operator-facing configuration point for the analysis stage.

Verdict routing

When the analysis Job completes, the controller stamps the summary onto Finding.status.investigation, sets the scheduling priority, and moves the phase:

Report says Finding moves to
ignore (false positive) Dismissed — alerts dismissed, issue closed
manual HandedOff — owners take over; /approve can revive
remediate, confidence < threshold or breaking-change hold AwaitingApproval
remediate, confidence ≥ threshold Queued
Stage outcome not ok Enhanced (retry) while attempts remain, then Failed

A partial report is never trusted: outcomes other than ok (runtime_error, timeout, budget_exceeded, report_missing, report_invalid) always retry or fail — they never route on whatever frontmatter survived.