Skip to content

Night-Orch Configuration Guide

This document explains how to write night-orch configuration files.

Source of truth for the schema is src/config/schema.ts. If this document and the code differ, treat the schema as authoritative and update this document.

Config File Discovery

night-orch resolves config in this order:

  1. --config <path> (if provided)
  2. .night-orch.yaml (only when --trust-workspace is set)
  3. .night-orch.yml (only when --trust-workspace is set)
  4. config.yaml
  5. config.yml
  6. ~/.night-orch/config.yaml
  7. ~/.night-orch/config.yml
  8. ~/.config/night-orch/config.yaml
  9. ~/.config/night-orch/config.yml

Recommended deployment uses a dedicated non-root user (for example orch) with:

  • config/state in /home/orch/.night-orch/
  • code in /home/orch/apps/night-orch
  • target repos in /home/orch/repos/*

Per-Repo Project Config (.night-orch.yml)

After the central config is loaded, night-orch checks each configured repos[].localPath for:

  1. .night-orch.yml
  2. .night-orch.yaml

If both files exist in the same repo, config load fails (ambiguous source).

Project config is deep-merged into central config with project values winning:

  • Repo-scoped keys merge into that repo entry only.
  • workflows and workerProfiles merge into top-level maps.
  • Objects merge recursively.
  • Arrays are replaced (not concatenated).

Project files are intended for repo-scoped settings and project-owned workflow/profile definitions.

Runtime Settings Overrides (DB-backed)

Night-orch supports DB-backed runtime overrides stored in SQLite (settings_overrides table).
Effective config precedence is:

  1. YAML value from central config, merged with per-repo project config (project wins where present)
  2. DB override (if present; applies only to runtime-overridable non-repo keys)

Overrides are persisted in DB and survive process restarts. They are not written back to YAML.

Runtime settings registry scope:

  • Includes all non-project-specific config keys used at runtime.
  • Excludes project-scoped repos[*] settings and schema marker version.
  • storage.dbPath is listed for visibility but is read-only at runtime (DB bootstraps before overrides load).
  • Sensitive fields are redacted in settings read surfaces (for example workerProfiles.*.env values).
  • JSON setting overrides are schema-validated per key before persistence.

Registered keys are visible via night-orch settings list (or Web/TUI Settings/MCP night-orch-list-settings). Current key groups:

  • github: tokenEnv, apiBaseUrl, pollIntervalSeconds, appMentions
  • storage: dbPath (read-only), worktreeRoot, logsRoot, autoCleanup.enabled, autoCleanup.intervalMinutes, retention.worktreeAgeDays, retention.detailDays, retention.archiveDays
  • notifications: channels, events.onRunStarted, events.onBlocked, events.onPrReady, events.onPrUpdated, events.onError, events.onRetryExhausted
  • loop: maxReviewIterations, maxTotalAgentPasses, stopOnPlannerFailure, requireVerificationPass, reviewApprovalKeyword, reviewNeedsChangesKeyword, blockOnAmbiguousReview, maxAutoRetries, maxEmptyDiffRetries, maxConsecutiveBlocks, decompose, maxSubtasks, maxConcurrentSubtasks
  • security: maxChangedFiles, maxChangedLines, maxDailyCostUsd, maxCostPerRunUsd
  • cost: model, subscriptionMetered, pricing.defaultModel, pricing.models
  • workerProfiles
  • metrics: enabled, port, host
  • observability: agentStreaming, eventRetention, sessionLogs, sessionLogRetention
  • mcp: enabled, transport, authTokenEnv, httpPort, httpHost
  • commentCommands: enabled, requireCollaborator
  • workflows

Keys not in the runtime registry — edit YAML and restart the daemon: ai.*, fileLoop.*, cost.allowEstimatedDuration, all repos[] settings, github.tokenEnv environment values (the registry exposes the env var name, not the token itself). Use night-orch daily-cost-override / night-orch cost-override for budget headroom rather than mutating security.maxDailyCostUsd at runtime.

Update surfaces:

  • CLI: night-orch settings list|set|unset
  • MCP: night-orch-list-settings, night-orch-set-setting, night-orch-clear-setting
  • Web: Settings page (/api/settings, /api/operations/settings/*)
  • TUI: settings tab (hotkey 5)

YAML Conventions

  • version must be exactly 1.
  • tokenEnv values are env var names, not literal tokens.
  • Path expansion (~, $VAR, ${VAR}) is applied to:
    • the config file path
    • storage.dbPath
    • storage.worktreeRoot
    • storage.logsRoot
    • repos[].localPath
  • CommandSpec fields accept either:
    • string: "pnpm test -- --run"
    • array: ["pnpm", "test", "--", "--run"]
  • repos[].labels.ready and repos[].labels.blocked accept either string or string array and are normalized to arrays.
  • Project config files (repos[].localPath/.night-orch.yml or .yaml) may define repo-scoped keys plus optional top-level workflows and workerProfiles.

Timestamp & Timezone Semantics

  • Night-orch treats all timestamps as UTC.
  • Runtime-generated timestamps use ISO-8601 UTC (YYYY-MM-DDTHH:mm:ss.sssZ).
  • Legacy SQLite-style timestamps without an explicit timezone (for example YYYY-MM-DD HH:mm:ss) are interpreted as UTC.
  • CLI/TUI time displays include an explicit UTC label.

Top-Level Schema

KeyTypeRequiredDefaultNotes
version1yesnoneSchema version.
githubobjectyesnoneGlobal forge/auth settings.
storageobjectnoobject with defaultsDB/worktree/log paths.
notificationsobjectnoobject with defaultsChannel/event notification config.
loopobjectnoobject with defaultsLoop decision limits and behavior.
fileLoopobjectnoobject with defaultsRepo-idle maintenance loop for low-risk file cleanup and review.
securityobjectnoobject with defaultsDiff/cost safety limits.
costobjectnoobject with defaultsCost model (pay-per-use enforces USD caps; subscription is advisory-only; subscription-metered tracks advisory USD with optional enforcement).
workerProfilesrecordno{}Named CLI profiles for agents.
metricsobjectnoobject with defaultsPrometheus exporter config.
observabilityobjectnoobject with defaultsLive agent event streaming/persistence settings.
mcpobjectnoobject with defaultsMCP server config for run/mcp commands.
commentCommandsobjectnoobject with defaultsIssue comment command processing config.
reposarrayyesnoneAt least one repo is required.
workflowsrecordno{}Named workflow definitions for custom pipelines.

github

KeyTypeRequiredDefaultNotes
tokenEnvstringyesnoneEnv var name holding GitHub token. Literal token prefixes (ghp_, ghs_, github_pat_) are rejected.
apiBaseUrlURL stringnohttps://api.github.comDefault base URL for GitHub repos.
pollIntervalSecondspositive numberno300Poll interval used by run loop.
appMentionsrecordno{}Mention templates keyed by mention alias (claude, codex, etc.).

github.appMentions.<key>

KeyTypeRequiredDefaultNotes
enabledbooleannofalseIf false, that mention key is filtered out even if requested by labels/defaults.
commentTemplatestringyesnoneTemplate used when posting mention comments; supports {issue}, {pr}, {repo} placeholders.

storage

KeyTypeRequiredDefaultNotes
dbPathstring pathno~/.config/night-orch/state.db
worktreeRootstring pathno~/code/.night-orch/worktreesnight-orch exports MISE_TRUSTED_CONFIG_PATHS with this path at startup, so any .mise.toml / mise.toml / .tool-versions checked out inside a worktree is automatically trusted by mise. Required for repos whose toolchain is managed by mise — otherwise bootstrap commands that invoke mise-shimmed tools (bundle, node, rake, ...) fail with "Config files ... are not trusted".
logsRootstring pathno~/code/.night-orch/logs
autoCleanupobjectnoobject with defaultsAutomatic cleanup settings.
retentionobjectnoobject with defaultsData retention periods.

storage.autoCleanup

KeyTypeDefaultNotes
enabledbooleantrueEnable automatic cleanup of stale worktrees and logs.
intervalMinutespositive number60How often auto-cleanup runs (in minutes).

storage.retention

KeyTypeDefaultNotes
worktreeAgeDayspositive number7Remove completed/error worktrees older than this.
detailDayspositive number30Retain detailed run data (events, phase data) for this many days.
archiveDayspositive number90Archive run records older than this.

notifications

KeyTypeRequiredDefaultNotes
channelsarrayno[ { type: "console" } ]Multiple channels allowed.
eventsobjectnoobject with defaultsPer-event toggle switches.

notifications.channels[]

Discriminated by type:

  • console
    • type: "console"
  • webhook
    • type: "webhook"
    • urlEnv: string (env var name containing webhook URL)
  • discord
    • type: "discord"
    • urlEnv: string (env var name containing Discord webhook URL)
  • smtp
    • type: "smtp"
    • host: string
    • port: positive int (default 587)
    • from: string
    • to: string
    • userEnv: string (env var name)
    • passEnv: string (env var name)
  • webpush (Phase 2c — Web Push notifications to subscribed browsers)
    • type: "webpush"
    • vapidPublicKeyEnv: string (env var name, public VAPID key)
    • vapidPrivateKeyEnv: string (env var name, private VAPID key)
    • vapidSubjectEnv: string (env var name, e.g. mailto:you@example.com)
    • Generate a keypair once with npx web-push generate-vapid-keys, export the three env vars on the daemon host, and the web UI's Settings page will expose an "Enable notifications" button. Any browser that subscribes receives background push notifications for configured events (blocked, pr_ready, error, retry_exhausted by default). Subscriptions are persisted in push_subscriptions and pruned automatically on 410 Gone.

notifications.events

KeyTypeDefault
onRunStartedbooleanfalse
onBlockedbooleantrue
onPrReadybooleantrue
onPrUpdatedbooleantrue
onErrorbooleantrue
onRetryExhaustedbooleantrue

loop

KeyTypeDefaultNotes
maxReviewIterationspositive number4Base max loop iterations before stop.
maxTotalAgentPassespositive number10Base max total worker passes.
stopOnPlannerFailurebooleantrueIf planner output fails, stop early instead of continuing.
requireVerificationPassbooleantrueIf true, verification failures block completion.
reviewApprovalKeywordstringAPPROVEDExpected reviewer verdict keyword.
reviewNeedsChangesKeywordstringCHANGES_REQUIREDExpected reviewer verdict keyword.
blockOnAmbiguousReviewbooleantrueParse failures in review phase become blocked state.
maxAutoRetriesint >= 03Auto-retry count for infrastructure errors.
maxEmptyDiffRetriesint 0-52Auto-retry count when coder produces no file changes.
maxConsecutiveBlocksint 1-204Circuit breaker: stop retrying after this many consecutive blocked runs on the same issue.
decomposebooleanfalseEnable automatic issue decomposition into sub-tasks.
maxSubtasksint 1-105Maximum sub-tasks per decomposition.
maxConcurrentSubtasksint 1-103Max parallel sub-task worktrees.

Note: loop limits are later triage-adjusted per issue (trivial/standard/architectural), so these are base values.

Decomposition

When decompose: true, issues classified as standard triage level with a body exceeding 500 characters (or containing 3+ numbered items/headings) are sent to the planner for decomposition. The planner decides whether to split the issue and outputs 2-5 atomic sub-tasks. Each sub-task runs the full Plan→Code→Verify→Review loop in its own git worktree. Sub-tasks execute in parallel waves based on their dependency graph, up to maxConcurrentSubtasks concurrent worktrees.

fileLoop

fileLoop configures a repo-scoped maintenance loop that runs only while the repo is otherwise idle. A session iterates through candidate files in a dedicated file-loop worktree, asks the configured reviewer profile to classify the next change, applies only trivial edits automatically, records larger follow-up ideas in loop.md, and publishes one PR when the session ends.

Operational constraints:

  • File-loop work runs only when a repo has no active issue runs.
  • Sessions are started and stopped explicitly through CLI, MCP, or the TUI file-loop tab.
  • Top-level fileLoop values provide defaults; repos[].fileLoop merges over them for per-repo overrides.
  • Final publish runs finalizeVerify; if verification fails, onFailure controls whether a draft PR is still opened.
KeyTypeDefaultNotes
enabledbooleanfalseMaster gate. night-orch file-loop start refuses to start when disabled for the repo.
maxDurationMinutespositive int480Hard wall-clock cap per session unless start --max-minutes overrides it.
maxIterationspositive int1000Upper bound on file-loop iterations per session.
minIntervalSecondsBetweenFilesint >= 05Cooldown before reconsidering the next file.
perIterationTimeoutSecondspositive int120Timeout for each reviewer worker invocation.
maxCostUsdnon-negative number5Session cost cap. Hitting it requests finalization.
maxFileLinespositive int1500Skip files larger than this line count.
includeGlobsstring[]["**/*.{ts,tsx,js,jsx,py,go,rs,md}"]Candidate file allowlist.
excludeGlobsstring[]built-in listCandidate file denylist. Defaults exclude generated artifacts, lockfiles, .git, and loop.md.
reviewerProfileKeystringclaude-cheapWorker profile name, or a worker type, used for file review iterations. Override this if your config does not define claude-cheap.
branchNameTemplatestringorch/file-loop/{repoSlug}/{yyyyMmDd}Supports {repoSlug} and {yyyyMmDd} placeholders.
loopMdPathstringloop.mdRepo-relative backlog file for deferred refactor notes.
commitPrefixstring[FILE-LOOP]Prefix used for per-file and loop.md commits.
perEditVerifyobjectobject with defaultsVerification run immediately after each trivial edit.
finalizeVerifyobjectobject with defaultsVerification run once before PR publication/finalization.

fileLoop.perEditVerify

KeyTypeDefaultNotes
enabledbooleantrueIf false, trivial edits are committed without per-file verification.
commandsstring[]["pnpm typecheck"]Commands run sequentially in the file-loop worktree.
timeoutSecondspositive int60Per-command timeout budget.

fileLoop.finalizeVerify

KeyTypeDefaultNotes
enabledbooleantrueIf false, publication does not run final verification.
commandsstring[]["pnpm typecheck", "pnpm lint"]Commands run sequentially before publication.
timeoutSecondspositive int300Per-command timeout budget.
onFailuredraft-pr | no-prdraft-prWhether to still open a draft PR when final verification fails.

security

KeyTypeDefaultNotes
maxChangedFilespositive number50Diff guard threshold.
maxChangedLinespositive number5000Diff guard threshold.
maxDailyCostUsdpositive number50Daily budget cap, enforced in pay-per-use and optionally in subscription-metered (cost.subscriptionMetered.enforceDailyLimit).
maxCostPerRunUsdpositive number10Per-run budget cap, enforced in pay-per-use and optionally in subscription-metered (cost.subscriptionMetered.enforcePerRunLimit).

Unblocking a run hit by a cost cap

These controls are interpreted by cost.model:

  • pay-per-use: always enforced
  • subscription: never enforced (USD advisory only)
  • subscription-metered: enforced only when cost.subscriptionMetered.enforcePerRunLimit and/or cost.subscriptionMetered.enforceDailyLimit are enabled

When a run is blocked by a cost limit, there are three escape hatches — pick whichever matches the scope of the situation:

  1. Whole day over budget → raise today's cap with night-orch daily-cost-override <amount>. Scoped to the current UTC day; auto-expires at 00:00 UTC. Use this when multiple queued issues would otherwise need individual overrides. Clear early with night-orch daily-cost-override --clear. Also exposed via MCP (night-orch-daily-cost-override) and TUI (% hotkey — doubles the current cap).
  2. One expensive run stuck → grant a per-run override with night-orch cost-override <repo> <issue> <amount>. Replaces the per-run cap for that one run and exempts it from the daily cap. Use when a single heavyweight issue needs more headroom than the daily cap would normally permit.
  3. Permanently raise the capnight-orch settings set security.maxDailyCostUsd <amount> (or security.maxCostPerRunUsd). This persists until explicitly cleared with night-orch settings unset, so reserve it for deliberate budget increases — not incident response.

cost

KeyTypeDefaultNotes
modelpay-per-use, subscription, or subscription-meteredpay-per-usepay-per-use enforces security.maxDailyCostUsd/security.maxCostPerRunUsd; subscription bypasses cost-limit blocking; subscription-metered logs advisory warnings and can optionally enforce caps via cost.subscriptionMetered.
allowEstimatedDurationbooleanfalseWhen false (default), worker runs that finish without parseable token usage block the attempt with tokenCaptureFailed instead of silently estimating cost from wall-clock duration. The duration estimate undercounted by 10–100× in production and was the root cause of inaccurate cost reports. Flip to true only as a temporary unblocker when a specific worker adapter genuinely cannot report token usage.
subscriptionMeteredobject{ advisoryThresholdUsd: null, enforcePerRunLimit: false, enforceDailyLimit: false }Controls warning/enforcement behavior for subscription-metered mode. Ignored for other models.
pricingobjectunsetOptional model-aware pricing table. When unset, built-in defaults are used (input $3/M, output $15/M, cache-read $0.3/M, fallback $0.008/min) for advisory/estimated USD.

cost.subscriptionMetered

KeyTypeDefaultNotes
advisoryThresholdUsdpositive number or nullnullLogs warnings when run/day estimated cost meets or exceeds this threshold.
enforcePerRunLimitbooleanfalseWhen true, applies security.maxCostPerRunUsd as a hard block in subscription-metered.
enforceDailyLimitbooleanfalseWhen true, applies security.maxDailyCostUsd as a hard block in subscription-metered.

cost.pricing

KeyTypeDefaultNotes
defaultModelstring"default"Fallback key when a worker's pricingModel/type has no direct pricing entry.
modelsrecord{}Per-model pricing map keyed by model name.

cost.pricing.models.<model>

KeyTypeDefaultNotes
inputUsdPerMillionTokensnon-negative number3Prompt/input token price in USD per 1,000,000 tokens.
outputUsdPerMillionTokensnon-negative number15Completion/output token price in USD per 1,000,000 tokens.
cacheReadUsdPerMillionTokensnon-negative number0.3Cached-input read token price in USD per 1,000,000 tokens.
minuteUsdnon-negative number0.008Time-based fallback price per minute when token counts are unavailable.

Validation notes:

  • cost.pricing.defaultModel must reference a key present under cost.pricing.models when models are provided.
  • workerProfiles.<name>.pricingModel must reference a key present under cost.pricing.models.

ai

Phase 3: direct-LLM API layer for night-orch's internal AI tasks — triage refinement, PR body summaries, reviewer parse salvage, and a bounded rebase-conflict resolver. This does NOT replace the Claude Code / Codex / opencode CLIs used for actual code-editing (planner, coder, reviewer); those keep running on the CLI path because they rely on the agentic tool-use loop that the direct API doesn't have. The conflict resolver is the narrow exception: it operates on one conflicted file at a time, validates the returned file, and falls back to the normal human block path on any failure.

When no ai.internal.enable.* flag is set the entire layer is a no-op and every consumer falls back to its pre-Phase-3 behavior (rule-based triage, template-only PR body, fail-closed reviewer parser). The conflict resolver is gated separately by autoResolveConflicts.enabled and ai.internal.features.conflictResolver.

ai.internal

KeyTypeDefaultNotes
provider"anthropic" | "openrouter" | "openai" | nullnullWhich direct-LLM backend to use. null disables the layer.
modelstring | nullnullModel id passed to the provider (e.g. "claude-3-5-sonnet-20241022" for Anthropic, "anthropic/claude-3.5-sonnet" for OpenRouter, "gpt-4o-mini" for OpenAI).
apiKeyEnvstring | nullnullEnv var name holding the API key. Refuses literal keys in YAML.
timeoutMspositive int30000Per-request timeout.
maxTokenspositive int1024Default max tokens per call. Each consumer may override.
features.conflictResolverbooleantrueEnables the internal-AI conflict resolver feature when autoResolveConflicts.enabled is also true. If provider/model/API key are missing at runtime, the resolver quietly falls back to the existing human block path and doctor reports it as unavailable.
enable.triagebooleanfalseLLM refines rule-based triage classification.
enable.reviewerParseFallbackbooleanfalseWhen the primary reviewer JSON parser fails, ask the LLM to salvage a structured verdict (CHANGES_REQUIRED or BLOCKED only — APPROVED is never inferred from free text).
enable.prBodybooleanfalsePrepends a 2-3 sentence plain-English summary to PR body bodies. The structured template still renders below.

Validation:

  • When any enable.* flag is true, all three of provider, model, and apiKeyEnv must be set. The schema rejects the config otherwise at load time.
  • apiKeyEnv must be an environment variable name, not a literal API key — the schema rejects values that look like inline secrets (sk-…, claude-…).

Security: AI API keys are added to the worker environment blacklist, so ANTHROPIC_API_KEY, OPENAI_API_KEY, OPENROUTER_API_KEY, and any ANTHROPIC_* / OPENAI_* / OPENROUTER_* env var are blocked from reaching CLI worker subprocesses. Pino redaction also scrubs apiKey and x-api-key fields from every log record.

Cost tracking: every AI call records through the same R4 cost ledger as CLI workers, tagged tokenSource='measured_api' and workerType='internal-ai' or workerType='internal-ai-conflict-resolver'. The /api/cost/health endpoint surfaces these as distinct funding sources so operators can see direct-API spend alongside CLI spend.

Example:

yaml
ai:
  internal:
    provider: anthropic
    model: claude-3-5-sonnet-20241022
    apiKeyEnv: ANTHROPIC_API_KEY
    features:
      conflictResolver: true
    enable:
      triage: true
      reviewerParseFallback: true
      prBody: true

autoResolveConflicts

Controls the bounded AI-assisted resolver that runs only after a queued rebase operation hits textual conflicts.

If resolution succeeds, night-orch continues the rebase, force-pushes the branch, and then follows the existing verify contract:

  • verify passes: the run returns to review_ready
  • verify fails: the coder loop runs as usual

If resolution fails, validation fails, the provider is unavailable, or the feature is disabled, night-orch aborts the rebase and blocks the run with the existing merge_conflict reason.

KeyTypeDefaultNotes
enabledbooleantrueMaster switch for the automated resolver pass.
maxAttemptsint 1-52Maximum number of resolve -> git add -> git rebase --continue cycles before falling back to the human block path.
maxFilesint 1-205Maximum number of conflicted files eligible for one automated attempt. Larger conflict sets skip auto-resolution and block immediately.

Example:

yaml
autoResolveConflicts:
  enabled: true
  maxAttempts: 2
  maxFiles: 5

workerProfiles

workerProfiles is a map of profile name to profile config.

Example:

yaml
workerProfiles:
  claude-default:
    type: claude
    command: claude
    args: ["-p"]

workerProfiles.<name>

KeyTypeRequiredDefaultNotes
typestringyesnoneAdapter type. Built-in: claude, codex, acp. OpenCode uses acp with command: opencode.
pricingModelstringnononeOptional model key used by cost.pricing.models for cost estimation. Falls back to type when omitted.
minuteUsdnon-negative numbernononeOptional profile-level duration fallback override used when token usage is unavailable.
commandstringyesnoneBinary to execute.
argsstring[]no[]Base CLI args for every task invocation.
workerTimeoutSecondspositive numberno1800Base timeout before triage scaling.
minimalEnvbooleannotrueDeprecated/ignored; worker env is always whitelist-based.
runtimeWrapperstring or nullnonullWrapper command prepended before command (for sandbox wrappers, etc.).
envrecord string->stringno{}Extra env vars for worker process; blacklist still applies.

Worker PATH is normalized at runtime: if missing, ~/.local/bin, ~/.local/share/pnpm, ~/.local/share/mise/shims, /usr/local/bin, /usr/bin, and /bin are appended.

repos[].agents references these profile names. Unknown profile references fail config load.

Authentication Considerations

Night-orch invokes the claude CLI as a subprocess — it does not handle authentication itself. The installed claude binary uses whatever auth is configured on the host (OAuth subscription login or API key).

For production / high-volume deployments, configure the claude CLI with an API key (ANTHROPIC_API_KEY) rather than a subscription OAuth login. As of April 2026, Anthropic restricts subscription OAuth to "ordinary, individual usage" of Claude Code and reserves the right to enforce this without notice. API key auth uses metered billing and is unaffected by these restrictions.

For personal dev-server usage, subscription OAuth login works fine and is fully supported — night-orch invokes the real claude CLI binary, not the API directly.

See Anthropic's legal and compliance docs for current policy on authentication methods.

ACP Adapter

The acp adapter type uses the Agent Client Protocol for agent-agnostic communication:

yaml
workerProfiles:
  gemini-acp:
    type: acp
    command: gemini     # acpx agent name
    args: []
    workerTimeoutSeconds: 1800

The command field specifies the acpx agent name (e.g., codex, claude, gemini, pi). ACPX resolves this to the correct ACP adapter. Supported agents include any ACP-compatible agent registered with acpx.

Requires acpx installed as a dependency (pnpm add acpx).

metrics

KeyTypeDefault
enabledbooleantrue
portpositive int9090
hoststring0.0.0.0

Notes:

  • For the default Docker-based monitoring stack, keep metrics.host: 0.0.0.0 so Prometheus can scrape the daemon from its container network.
  • metrics.enabled is runtime-overridable (night-orch settings set metrics.enabled ...). night-orch status reports when runtime state diverges from YAML.

observability

KeyTypeDefaultNotes
agentStreamingbooleantrueEnable live worker event emission and persistence.
eventRetentionint (100-10000)1000In-memory max agent events retained per run.
sessionLogsbooleantrueWrite per-phase JSONL session logs to storage.logsRoot/<runId>/.
sessionLogRetentionpositive int7Retention target in days for session logs (consumed by cleanup policy).

mcp

KeyTypeDefaultNotes
enabledbooleanfalseWhen true, run starts the embedded MCP HTTP server (dual transport — see below).
transportstdiostdioReserved. The standalone night-orch mcp command speaks stdio; the HTTP server started by run/web exposes streamable HTTP and legacy SSE on the same port regardless of this value.
authTokenEnvstring or nullnullName of an environment variable holding a bearer token. When set and the env var is non-empty, every MCP request must present a matching Authorization: Bearer … header. Required when httpHost is non-loopback.
httpPortpositive int3100Port the embedded MCP server listens on.
httpHoststring127.0.0.1Host to bind. Loopback (127.0.0.1, ::1, localhost) is always allowed; any other host requires authTokenEnv to be set.

Transports

The embedded MCP server exposes both transports on the same port so old and new clients can coexist:

  • Streamable HTTP (modern) — POST /mcp, with Mcp-Session-Id response/request header for session routing. Also GET /mcp (server-initiated SSE stream) and DELETE /mcp (client-initiated session teardown). This is the transport Claude Code's type: "http" client speaks.
  • Legacy SSEGET /sse for the session handshake followed by POST /mcp?sessionId=… for follow-up JSON-RPC messages. Kept for backwards compatibility with existing proxies and older MCP clients.

A liveness probe is available at GET /health and does not require auth.

Exposing MCP over a private network

To let a remote Claude Code instance connect directly (e.g. over Tailscale), bind to a non-loopback address and configure a strong bearer token:

yaml
mcp:
  enabled: true
  httpHost: 100.94.242.23    # e.g. Tailscale IP
  httpPort: 8808
  authTokenEnv: NIGHT_ORCH_MCP_TOKEN
bash
export NIGHT_ORCH_MCP_TOKEN=$(openssl rand -hex 32)

Client-side .mcp.json:

json
{
  "mcpServers": {
    "night-orch": {
      "type": "http",
      "url": "http://100.94.242.23:8808/mcp",
      "headers": { "Authorization": "Bearer ${NIGHT_ORCH_MCP_TOKEN}" }
    }
  }
}

Non-loopback binding without authTokenEnv is rejected at startup — exposing mutation tools to an unauthenticated listener is never a supported configuration.

commentCommands

KeyTypeDefaultNotes
enabledbooleantrueEnable processing of /orch commands in issue comments.
requireCollaboratorbooleantrueOnly repo collaborators can use comment commands. Set to false only for private repos where all commenters are trusted.

Supported commands (posted as issue comments):

  • /orch retry — start a fresh retry from the latest base branch
  • /orch rebase — queue an explicit rebase of the work branch onto the latest base
  • /orch cancel — cancel an active run
  • /orch continue — resume the existing branch with fresh context for blocked/review-ready/errored runs

When a PR becomes non-mergeable while it is in review_ready, night-orch does not treat that as a generic continue. It queues a dedicated branch refresh attempt that uses the repo's updateStrategy, and if that refresh conflicts the blocked run stores a durable conflict snapshot for the next /orch continue pass.

workflows

Named workflow definitions for custom execution pipelines. When no workflow is configured:

  • standard issues use Plan→Code→Verify→Review→Decide
  • trivial issues use a lightweight Code→Verify→Decide flow (review gate disabled)
yaml
workflows:
  minimal:
    steps:
      - { type: worker, id: code, role: coder }
      - { type: verify, id: verify }
      - { type: worker, id: review, role: reviewer }
      - { type: decide, id: decide, onIterate: code }

  fast-trivial:
    roles:
      coder: codex
      reviewer: codex
    agents:
      codex: codex-fast
    steps:
      - { type: worker, id: code, role: coder }
      - { type: verify, id: verify }
      - { type: decide, id: decide, onIterate: code, requireReview: false }

  with-security:
    steps:
      - { type: worker, id: plan, role: planner, skipWhen: trivial }
      - { type: worker, id: code, role: coder, continueFrom: plan }
      - { type: verify, id: verify }
      - { type: worker, id: security, role: reviewer, prompt: security-review.md }
      - { type: worker, id: review, role: reviewer }
      - { type: decide, id: decide, onIterate: code }

Step Types

TypeFieldsDescription
workerid, role, skipWhen?, continueFrom?, prompt?Invoke a worker adapter. Built-in roles: planner, coder, reviewer.
verifyid, skipWhen?Run configured verify commands.
decideid, onIterate, requireReview?Evaluate review/verify results and route to publish, iterate (jump to onIterate step), or block.
  • skipWhen — skip the step when the triage level matches (e.g., trivial)
  • continueFrom — continue the AI session from a prior step (e.g., coder continues planner's session). Session reuse is agent-specific; cross-agent handoffs (for example planner=claude, coder=codex) start a fresh session.
  • prompt — path to a custom system prompt template (overrides the default)
  • requireReview — default true; set to false for no-review workflows (for example lightweight triage paths)

Workflow-Level Overrides

  • roles — optional role defaults (planner/coder/reviewer) for runs using this workflow
  • agents — optional per-agent worker profile overrides (same shape as repos[].agents)

Reference a workflow in repos[].workflow by name.

repos[]

KeyTypeRequiredDefaultNotes
repoowner/name stringyesnoneRepository slug.
forgegithub or forgejonogithubForge implementation selector.
linkedProjectsowner/name string[]no[]Additional issue-source repos to discover from using this repo's selectors/flow.
apiBaseUrlURL stringnononeRequired for forgejo; optional override for github.
tokenEnvstringnononeToken env override per repo.
maxConcurrentRunsint 1-20no1Max issues processed concurrently for this repo per poll cycle.
localPathstring pathyesnoneLocal repo checkout path.
baseBranchstringnomainPR target branch.
branchPrefixstringnoorchWork branch prefix.
updateStrategymerge | rebasenomergeHow normal queued work incorporates upstream base branch changes by default. merge creates merge commits (reliable for automated systems). rebase replays commits for linear history (use only if your repo requires linear history). This setting is used by automatic branch refreshes, merge-conflict follow-up attempts, and publish-time branch reconciliation. Manual retry, continue, and rebase actions can override it per action from the CLI, TUI, MCP, or web UI; explicit rebase still defaults to rebase unless overridden.
labelsobjectnoobject with defaultsOrchestration label names.
kanbanobjectnononeOptional alternate state-label flow activated by a trigger label.
labelConfigrecordno{}Label metadata overrides for labels-init.
defaultsobjectnoobject with defaultsDefault roles + mention settings.
planningobjectnoobject with defaultsPlanning-only mode settings (PRD path).
fileLoopobjectno{}Per-repo overrides merged onto top-level fileLoop.
environmentobjectnononeShared/dedicated env setup.
verifyCommandSpec[]no[]Verify commands run in worktree.
promptsobjectnononeOptional custom system prompt template paths.
selectorsobjectnoobject with defaultsIssue label inclusion/exclusion filters.
agentsrecordno{}Maps agent names to worker profile names.
workflowstringnononeName of a workflow from workflows section. Uses default pipeline if omitted.
workflowByTriageobjectnononePer-triage workflow selection (trivial/standard).
mergeQueueobjectnoobject with defaultsMerge queue configuration.

Poll execution model:

  • Repos are polled in parallel.
  • Each repo runs up to maxConcurrentRuns issues at once (default 1).

Project-local repo overrides

You can move repo-specific settings into a file inside the repository checkout:

yaml
# <repo>/.night-orch.yml
workflow: project-fast
defaults:
  coder: codex
environment:
  bootstrap:
    - command: pnpm install
      when: always

workflows:
  project-fast:
    steps:
      - { type: worker, id: code, role: coder }
      - { type: decide, id: decide, onIterate: code }

This file is merged with the matching repos[] entry from central config.

repos[].workflowByTriage

Route triage levels to different named workflows:

yaml
repos:
  - repo: myorg/myrepo
    workflow: full
    workflowByTriage:
      trivial: fast-trivial
      standard: full

Resolution order:

  1. Planning-label workflow override (planning-only mode)
  2. workflowByTriage[triageLevel]
  3. workflow
  4. Built-in defaults (trivial -> lightweight, others -> full)

Note: architectural issues are intentionally handled outside workflow execution and are labeled for human guidance.

repos[].labels

KeyTypeDefaultNotes
readystring or string[]['orch:ready']Normalized to array.
runningstringorch:running
blockedstring or string[]orch:blockedNormalized to array.
needsHumanstringorch:needs-human
reviewReadystringorch:review-ready
errorstringorch:error
retrystringorch:retry
planningstringorch:planningWhen present on an issue, night-orch switches to planning-only mode and publishes only a PRD markdown file.
mergeQueuedstringorch:merge-queuedSet when PR enters the merge queue.
mergingstringorch:mergingSet while staging branch CI is running.
mergeFailedstringorch:merge-failedSet when the merge queue identifies this PR as the culprit.

repos[].linkedProjects

List of additional repositories to use as issue sources for the repo.

Example:

yaml
repos:
  - repo: myorg/app
    linkedProjects:
      - myorg/tracker
      - myorg/platform-triage

Each entry must use owner/name format.

repos[].kanban

Optional alternate state flow. When triggerLabel is present on an issue, night-orch uses kanban.labels for status transitions (queued/running/blocked/review/error/retry) instead of repos[].labels.

yaml
repos:
  - repo: myorg/myrepo
    kanban:
      triggerLabel: flow:kanban
      labels:
        ready: [kanban:todo]
        running: kanban:doing
        blocked: kanban:blocked
        needsHuman: kanban:needs-human
        reviewReady: kanban:review
        error: kanban:error
        retry: kanban:retry
        planning: kanban:planning
        mergeQueued: kanban:merge-queued
        merging: kanban:merging
        mergeFailed: kanban:merge-failed

repos[].labelConfig

Map of label name to optional metadata used by night-orch labels-init.

Each entry supports:

KeyTypeRequiredNotes
color6-char hex stringnoExample: 0E8A16.
descriptionstring (<= 100 chars)no

Constraint: each entry must include at least one of color or description.

repos[].defaults

KeyTypeDefaultNotes
plannerclaude, codex, or opencodeclaudeDefault planner role assignment.
coderclaude, codex, or opencodeclaudeDefault coder role assignment.
reviewerclaude, codex, or opencodeclaudeDefault reviewer role assignment.
doneModepr-ready or manual-onlypr-readyReserved for workflow policy; currently not consumed in runtime logic.
notifyPrioritynormal or highnormalReserved for notification priority; currently not consumed in notifier routing.
prMentionsstring[][]Mention aliases posted on PRs by default.

Role labels can override these defaults per issue:

  • plan:claude / plan:codex / plan:opencode
  • code:claude / code:codex / code:opencode
  • review:claude / review:codex / review:opencode

Planning-only mode label:

  • orch:planning (or whatever repos[].labels.planning is set to)

When this label is present, night-orch uses a planning-only workflow and must produce a PR containing exactly one PRD markdown file.

repos[].planning

KeyTypeDefaultNotes
prdDirectorystringdocs/prdRepository-relative directory where planning-mode PRD files are created.

repos[].fileLoop

Repo-level file-loop overrides merge onto the top-level fileLoop block for that repo only.

Example:

yaml
fileLoop:
  enabled: false
  reviewerProfileKey: claude-default

repos:
  - repo: myorg/myrepo
    fileLoop:
      enabled: true
      maxDurationMinutes: 180
      reviewerProfileKey: codex-default
      includeGlobs:
        - "src/**/*.{ts,tsx}"
      excludeGlobs:
        - "src/generated/**"

Repo overrides support the same keys as top-level fileLoop, but every field is optional. Nested perEditVerify and finalizeVerify objects merge field-by-field rather than replacing the entire object.

repos[].environment

KeyTypeRequiredDefaultNotes
defaultModeshared or dedicatednosharedBase env mode.
dedicatedobjectnononeRequired if dedicated mode is used.
sharedobjectnononeShared mode behavior.
bootstrapcommand object[]no[]Runs during setup (always/shared/dedicated).
cleanupcommand object[]no[]Runs during dedicated teardown.

Issue labels can force mode per run:

  • env:shared
  • env:dedicated

repos[].environment.dedicated

KeyTypeRequiredDefaultNotes
compose.filestringyesnoneCompose file path used in worktree.
compose.servicesstring[]no[]Optional service subset.
compose.projectNamestringnoorch-{issue}{issue} placeholder supported.
env.copyFromstringno.envBase env file copied from repo root.
env.overridesrecordno{}Values support {issue} and {auto:min-max} port token.
env.overrideFilesstring[]no[]Additional env files appended in order.
healthcheckCommandSpecnononeSupports {port} placeholder after auto-port allocation.
teardownOnCompletebooleannotrueIf true, compose stack is stopped after run.

repos[].environment.shared

KeyTypeDefaultNotes
requireRunningbooleantrueIf true, failed healthcheck aborts run.
healthcheckCommandSpecnoneCommand to verify shared stack is up.

repos[].environment.bootstrap[] and cleanup[]

KeyTypeRequiredDefaultNotes
commandCommandSpecyesnoneExecuted in worktree directory.
whenalways | shared | dedicatednoalwaysMode filter.
failureHintsobject[]no[]Optional pattern-based hints appended to bootstrap error output.
failureHints[]
KeyTypeRequiredDefaultNotes
containsstringyesnoneSubstring to match against command output.
messagestringyesnoneHint text shown when the pattern matches.
outputcombined | stdout | stderrnocombinedWhich output stream(s) to inspect.

repos[].verify

Array of commands executed sequentially in worktree. Failures are collected per command; verification result is evaluated after all commands run.

repos[].prompts

KeyTypeRequiredNotes
plannerSystemstring pathnoIf file exists, used instead of default planner system prompt.
coderSystemstring pathnoIf file exists, used instead of default coder system prompt.
reviewerSystemstring pathnoIf file exists, used instead of default reviewer system prompt.

If a configured template file is missing, a warning is logged and built-in defaults are used.

repos[].selectors

KeyTypeDefaultNotes
includeLabelsAnystring[]['orch:ready']Issue must include at least one (empty list means include all).
excludeLabelsAnystring[]['orch:blocked', 'orch:error', 'orch:needs-human']Issue is skipped if any label matches.

repos[].agents

Map of agent name to worker profile name.

Typical shape:

yaml
agents:
  claude: claude-default
  codex: codex-default
  opencode: opencode-qwen

Resolution behavior:

  1. If mapping exists and profile exists, that profile is used.
  2. Otherwise, night-orch falls back to first profile whose type matches the role agent (claude/codex/opencode).
  3. If no matching profile exists, the run fails.

OpenCode runs through the acp adapter with command: opencode. The target repo must have an opencode.json defining available models and provider config. To select different models per role, use OPENCODE_CONFIG_CONTENT in the worker profile's env to override the default model:

yaml
workerProfiles:
  opencode-qwen:
    type: acp
    command: opencode
    env:
      OPENCODE_CONFIG_CONTENT: '{"model":"openrouter/qwen/qwen3-coder"}'
  opencode-kimi:
    type: acp
    command: opencode
    env:
      OPENCODE_CONFIG_CONTENT: '{"model":"openrouter/moonshotai/kimi-k2.5"}'

OpenCode reads API credentials from its own auth store (~/.local/share/opencode/auth.json, configured via opencode /connect). Since HOME is on the worker env whitelist, no additional env changes are needed.

repos[].mergeQueue

Bors-style merge queue that batches approved PRs, tests them together, and bisects on failure.

KeyTypeDefaultNotes
enabledbooleanfalseEnable the merge queue for this repo.
batchSizeint 1-205Max PRs per batch.
mergeMethodmerge | squash | rebasemergeGit merge strategy for staging branch.
retryFlakyOncebooleantrueRetry a failed batch once before bisecting.
requireApprovalbooleantrueRequire human PR approval before entering queue.
stagingBranchPrefixstringorch/stagingPrefix for staging branches.

When enabled, each poll cycle:

  1. Checks for an active staging batch — if CI passed, fast-forwards base branch
  2. If CI failed, bisects the batch (halves it, tests each half) until the culprit PR is identified
  3. If no active batch, scans for eligible PRs (review_ready + CI passing + approved) and forms a new batch
  4. Conflicting PRs are ejected from the batch and continue to the next eligible PR

Labels used: orch:merge-queued, orch:merging, orch:merge-failed.

Forge-Specific Notes

  • forge: github
    • token env: repos[].tokenEnv if present, otherwise github.tokenEnv
    • API base URL: repos[].apiBaseUrl if present, otherwise github.apiBaseUrl
  • forge: forgejo
    • token env: repos[].tokenEnv if present, otherwise FORGEJO_TOKEN
    • repos[].apiBaseUrl is required

Mention Behavior

Mentions posted to PR comments are resolved from:

  1. issue labels: pr-mention:<key>
  2. repo defaults: repos[].defaults.prMentions
  3. global gating: github.appMentions.<key>.enabled (disabled entries are removed)

Comment body is github.appMentions.<key>.commentTemplate if configured, otherwise @<key>.

Examples