junco
contents

Configuration

junco reads one JSON file: ~/.junco/config.json, resolved from the environment only — the working directory never matters, and the deprecated --config flag is ignored. A legacy ~/.config/junco/config.json (respects XDG_CONFIG_HOME) keeps working until junco data migrate moves it. Every field is optional — omit a key and its default applies. The guided way to produce the file is the setup walkthrough (junco dashboard on a first run); the headless way is junco config init.

Levers marked live apply on the daemon's next poll after the file changes — no restart. Levers marked restart need junco restart; junco status lists any pending ones. Inspect and edit from the command line:

junco config list
junco config get worker.pollIntervalSeconds
junco config set worker.dailyBudgetUsd 5

A minimal config is the empty object {} — local endpoint, sandbox on, GitHub bridge off. A typical hosted-catalog setup touches three things:

{
  "model": { "id": "<provider>/<model-name>" },
  "worker": { "dailyBudgetUsd": 5 },
  "github": {
    "enabled": true,
    "repos": [{ "nwo": "acme/reef-api", "path": "~/code/reef-api" }]
  }
}
note — catalog model ids are provider-prefixed; the walkthrough and junco config list print the real ones. An unset model.baseUrl means the local default, http://127.0.0.1:1234/v1 — any OpenAI-compatible endpoint works.

Deprecated path keys (vaultRoot, juncoSubdir, observability.stateDir, git.worktreeRoot, github.externalReposRoot) still work, warn once at startup, and are flagged by junco doctor; junco data migrate unifies them under dataDir. TOML configs are no longer read — a nearby config.toml is a startup error, not a silent ignore.

Environment variables

JUNCO_LOG_JSON=1
Force JSON log lines even on a TTY.
GH_CONFIG_DIR
Injected by the daemon for bot-account GitHub traffic; not yours to set.
$VISUAL / $EDITOR
Editor opened by junco analyze edit.
XDG_CONFIG_HOME
Relocates the default config directory.
provider key vars
Hosted catalog models read their provider's conventional env var when model.apiKey is unset; model.apiKey also accepts a literal or a "$ENV_VAR" reference.

Every lever

Generated from junco config list at the version stamped below — names, types, defaults, and descriptions are junco's own. read-only marks structured levers you edit in the file directly rather than via junco config set.

top-level

dataDir string
Unified data root — queue, reviews, outbox, mirror, clones, worktrees, transcripts. default undefined · restart

Everything junco keeps on disk resolves under this one root (default ~/.local/state/junco):

<dataDir>/
  queue/            inbox · processing · done · failed
  review/           parked assess findings · parked analyze drafts
  outbox/           queued GitHub writes (+ dead/ after 3 attempts)
  clones/           watched/ and external/ managed checkouts
  worktrees/        ephemeral per-ticket build worktrees
  transcripts/      <ticket-id>.jsonl per-run event streams
  watchlist.json · spend.json · worker.log · migrated.json

The root writes itself a .gitignore (*), so pointing dataDir inside a git checkout can never dirty a commit. junco data prints the live tree with counts and provenance; junco data migrate folds any legacy-key layout into it.

vaultRoot string
Root directory Junco keeps its ticket queue under. default undefined · restart
juncoSubdir string
Subdirectory under vaultRoot holding inbox/processing/done/failed. default "Junco" · restart
tools structured, read-only
Tool allowlist granted to the coding agent. default ["read","bash","edit","write","grep","find","ls"] · live
updateCheck boolean
Enable periodic update checks for new Junco releases. default true · live

model.*

model.id string
Provider-prefixed model id, e.g. <provider>/<model-name>. default "local/my-model" · live
model.source string
Model resolution mode: auto (catalog for non-local providers without an explicit baseUrl), catalog, or inline. default "auto" · live
model.modelsJson string
Path to a Pi-style models.json; when set, provider+model load from that file and the inline capability fields are ignored. default undefined · live
model.api string
Pi API style, e.g. openai-completions. default "openai-completions" · live
model.baseUrl string
OpenAI-compatible /v1 endpoint base URL. Unset = local default (http://127.0.0.1:1234/v1); setting it forces inline resolution. default undefined · live
model.apiKey secret
API key for the inference endpoint. Literal, "$ENV_VAR" reference, or unset to use the provider's env var (e.g. <PROVIDER>_API_KEY) for hosted catalog models. default undefined · live
model.retry.maxRetries number
SDK auto-retry attempts on transient provider errors; unset = SDK default (3). default undefined · live
model.retry.baseDelayMs number
Base delay for SDK auto-retry backoff in ms; unset = SDK default (2000). default undefined · live
model.reasoning boolean
Whether the model supports reasoning/thinking tokens. default true · live
model.input structured, read-only
Accepted input modalities for the model. default ["text","image"] · live
model.contextWindow number
Model context window size in tokens. default 131072 · live
model.maxTokens number
Max output tokens per model call. default 49152 · live
model.cost.input number
Cost per input token, for cost tracking/reporting. default 0 · live
model.cost.output number
Cost per output token, for cost tracking/reporting. default 0 · live
model.cost.cacheRead number
Cost per cache-read token, for cost tracking/reporting. default 0 · live
model.cost.cacheWrite number
Cost per cache-write token, for cost tracking/reporting. default 0 · live
model.thinkingLevel string
Worker default reasoning/thinking level. default "medium" · live
model.thinkingLevelMap structured, read-only
Thinking-level → chat-template effort-vocabulary translation; an override replaces the default map wholesale. default {"minimal":"low","low":"low","medium":"medium","high":"xhigh","xhigh":"xhigh"} · live
model.compat structured, read-only
Provider compatibility overrides (developer role, reasoning effort, thinking format, etc.), merged over junco's defaults. default {} · live

worker.*

worker.defaultTimeoutMinutes number
Default per-ticket execution timeout in minutes. default 30 · live
worker.pollIntervalSeconds number
Seconds between inbox polls when idle. default 15 · live
worker.startupPollSeconds number
Seconds the daemon waits/polls at startup before normal polling begins. default 30 · live
worker.startupWait boolean
Whether the daemon waits at startup for crash-recovery grace before claiming. default true · live
worker.endpointProbe auto | always | never
Endpoint probe policy: auto (probe local/inline, skip hosted catalog), always, or never. default "auto" · live
worker.maxTransientRetries number
Max retries for transient failures before a ticket is marked failed. default 2 · live
worker.retryBackoffSeconds number
Backoff seconds before a requeued ticket becomes eligible again. default 60 · live
worker.maxConcurrent number
Parallel ticket slots; same-repo tickets always serialize (restart to apply — the serial-vs-scheduler mode fork is chosen once at startup). default 1 · restart
worker.commitLeftovers boolean
Sweep uncommitted leftovers into a final commit at run end. default false · live
worker.applyFallbackToAgent boolean
When a patch ticket's junco-patch series fails to apply, or applies cleanly but its own Verification block then fails, fall back to the agent (patch as spec) instead of failing the ticket terminally. default true · live
worker.dailyBudgetUsd number
Daily USD spend cap; claiming pauses once reached until local midnight (0 disables). default 0 · live
note — every agent session's cost lands in a per-day ledger (spend.json, surfaced on /health and the dashboard). At the cap, claiming pauses — loudly, without burning any ticket's retry budget — and resumes at local midnight or when you raise the cap. 0 disables the cap but keeps the ledger.

supervisor.*

supervisor.enabled boolean
Enable the supervisor (nudge/escalate/kill loop-guard). default true · live
supervisor.budgetPerKind number
Budget of guard interventions allowed per kind before escalation. default 1 · live
supervisor.escalationWindowTurns number
Turn window over which supervisor escalation is evaluated. default 3 · live
supervisor.outputBudgetPerTurn number
Max output budget per turn before the supervisor nudges. default 12000 · live
supervisor.outputBudgetPostCommit number
Max output budget per turn once a commit has landed. default 24000 · live

git.*

git.gitBin string
Path/name of the git binary to invoke. default "git" · live
git.ghBin string
Path/name of the GitHub CLI (gh) binary to invoke. default "gh" · live
git.defaultBaseBranch string
Default base branch PRs are opened against. default "main" · live
git.branchPrefix string
Prefix applied to branches Junco creates. default "junco/" · live
git.worktreeRoot string
Deprecated: overrides dataDir for per-ticket git worktrees only. default undefined · live
git.removeWorktreeOnSuccess boolean
Remove the git worktree after a successful run. default true · live
git.allowedRepoRoots structured, read-only
Containment rail: when non-empty, PR-flow tickets may only target repos under these roots ([] = anywhere). default [] · live
note — the containment rail for the folder inbox: with roots set, a PR-flow ticket whose repo: lies outside them fails plan-lint before any agent runs. An empty list means any path — fine for a single-user machine, worth narrowing wherever anything else can write tickets.

pr.*

pr.draftByDefault boolean
Open PRs as drafts by default. default true · live
pr.defaultLabels structured, read-only
Labels applied to every PR Junco opens. default [] · live
pr.secretScan boolean
Scan the diff about to be pushed for secret shapes; fail the ticket on a hit. default true · live

verify.*

verify.enabled boolean
Run the verify (build/test) phase before opening a PR. default true · live
verify.commandTimeout number
Timeout in seconds for the verify command. default 60 · live
verify.blockOnFail boolean
Block PR creation when verify fails (vs. warn and continue). default false · live
verify.sandboxed boolean
Run ## Verification blocks under the ticket's sandbox (false = your repo's code runs unconfined). default true · live

sandbox.*

Agent tool subprocesses run OS-confined: writes jailed to the worktree plus scratch, reads of secret paths denied, environment scrubbed, network egress denied unless a ticket sets network: true. Backends: Seatbelt on macOS (built in), bubblewrap on Linux (apt/dnf install bubblewrap).

warn — the sandbox fails closed. On Linux without bwrap the worker refuses to run tickets rather than run them unconfined; install bubblewrap or set sandbox.backend: "none" deliberately.
sandbox.enabled boolean
Wrap agent tool subprocesses in an OS sandbox (env scrub + path jail). default true · live
sandbox.backend auto | seatbelt | bwrap | none
Sandbox backend: auto (seatbelt on macOS, bwrap on Linux), a forced one, or none. default "auto" · live
sandbox.requireBackend boolean
With backend=auto, fail closed when the OS backend probe fails (tickets refuse, doctor ✗) instead of degrading to none. default false · live
sandbox.network deny | allow
Default network egress for agent tool subprocesses (per-ticket network:true widens it). default "deny" · live
sandbox.extraDenyRead structured, read-only
Extra absolute paths whose reads are denied (added to the built-in secret deny-list). default [] · live
sandbox.extraAllowWrite structured, read-only
Extra absolute paths where writes are permitted (added to worktree + scratch). default [] · live
sandbox.bashTimeoutSeconds number
Ceiling (seconds) on one sandboxed bash call when the agent passes no timeout; 0 = none, max 2147483. The agent's explicit timeout wins when positive and finite. Live for sessions spawned after the change. default 600 · live

critic.*

critic.enabled boolean
Run the post-session critic review pass before finalizing a PR. default true · live
critic.maxRetries number
Max critic-requested revision retries. default 1 · live
critic.thinking string
Thinking level used for critic review calls. default "minimal" · live

planLint.*

planLint.enabled boolean
Run plan-lint checks on ticket plans before execution. default true · live
planLint.blockOnError boolean
Block execution when plan-lint finds errors (vs. warn). default true · live
planLint.checkLabels boolean
Include label checks in plan-lint. default true · live

observability.*

observability.healthEnabled boolean
Enable the /health metrics HTTP server. default true · restart
observability.healthHost string
Bind address for /health; non-loopback exposes metrics (restart to rebind). default "127.0.0.1" · restart
observability.healthPort number
Port the /health metrics server binds (restart to rebind). default 8787 · restart
observability.logLevel debug | info | warn | error
Daemon-wide log threshold (applied live). default "info" · live
observability.stateDir string
Deprecated: overrides dataDir for the entire data root. default undefined · restart
observability.logToFile boolean
Write daemon logs to a file under dataDir. default true · restart
observability.transcripts boolean
Write per-ticket event transcripts under dataDir. default true · restart

github.*

github.enabled boolean
Enable the GitHub issues→inbox bridge (disabled by default: zero gh calls). default false · restart
github.triggerLabel string
Approval label that triggers the PR flow; lifecycle labels derive from it. Restart to apply — the reporter bakes in the label prefix at startup. default "junco" · restart
github.askLabel string
Issue label that routes to the read-only Q&A path (defaults to `<triggerLabel>:ask`). Restart to apply (see triggerLabel). default undefined · restart
github.pollIntervalSeconds number
GitHub bridge sweep cadence, independent of the worker poll interval. default 60 · live
github.requireApproval boolean
When false, a plan-ready GitHub ticket auto-executes on the next sweep. default true · live
warn — setting this false removes the human gate: any plan-ready issue executes on the next sweep. On a repo with other collaborators, someone with triage-only access could steer work they could never approve. Keep it true anywhere that isn't a private personal repo.
github.plannerModelId string
Planning-session model id override for GitHub tickets (same endpoint). default undefined · live
github.externalReposRoot string
Root directory for managed clones of unowned repos (fork-PR flow). default undefined · live
github.repos structured, read-only
Explicit list of watched GitHub repos (owner/repo name-with-owner + local path). default [] · live

botAccount.*

botAccount.enabled boolean
Act as a dedicated bot account for all daemon GitHub traffic (log it in with: junco auth login). default false · restart
botAccount.configDir string
Isolated gh config dir holding the bot login (GH_CONFIG_DIR for daemon gh/git). default "~/.junco/gh" · restart

planSets.*

planSets.enabled boolean
Enable plan-driven ticket compilation (Layer 2; Layer-1 dependency machinery is always on). default false · restart
planSets.mergePollSeconds number
Cadence for the plan-set merge sweep that stamps satisfied dependencies and cascades failed ones. default 60 · live
planSets.maxTasks number
Layer-2 compiler cap on tasks per plan set (reserved). default 10 · live

chat.*

chat.enabled boolean
Enable the dashboard chat (/chat/* routes on the health server). default true · live
chat.modelId string
Chat model id override (same endpoint); unset → github.plannerModelId, then model.id. default undefined · restart
chat.thinkingLevel string
Chat thinking level; unset → model.thinkingLevel. default undefined · restart
chat.turnTimeoutMinutes number
Per-turn chat timeout; unset → worker.defaultTimeoutMinutes. default undefined · restart
chat.submitTool boolean
Register the junco_submit tool on new chat sessions (the model can submit a parked draft after you confirm). default true · restart
chat.confirmTimeoutMinutes number
How long a proposed chat submit waits for y/n in the dashboard before expiring. default 10 · restart
chat.thinkTags auto | on | off
Split inline <think>…</think> spans from the answer into the thinking block; auto skips a turn that already streams native thinking. A running chat session keeps the value it started with (restart, or /new in the chat, to apply). default "auto" · restart
chat.maxFps number
Dashboard frame cap while the chat streams (lower on a slow terminal or machine). default 60 · restart
chat.theme auto | dark | light
Palette for the chat's highlighted code fences: auto guesses from the terminal background (COLORFGBG), dark/light pin it. default "auto" · restart

assess.*

assess.maxIssuesPerRun number
Cap on issues filed per `junco audit` run. default 20 · live
assess.minSeverity critical | high | medium | low
Drop assessment findings below this severity. default "low" · live
assess.npmBin string
Binary for the dependency scan (npm audit --json). default "npm" · live
assess.fileAs me | bot
Identity `junco audit file` posts under: me (your gh login) or bot (the dedicated bot account; fails loud when its login is missing). default "me" · live

skills.*

skills.harnessDirs structured, read-only
Harness skills dirs that receive a junco-dispatch symlink (standing consent; managed by 'junco skill install' and the wizard). default [] · restart