Configuration
Detent separates machine settings, instance settings and project workflows:
global.yamlcontains machine settings only for an enrolled runner, which reads~/.config/detent-runner/global.yaml.- Cloud stores project membership, allowed projects, project rank and organization/project model selection in its database.
- Each project has a checked-in
detent.yamlmachine contract, with optional machine-local overrides indetent.local.yaml. The project contract can also live in legacyWORKFLOW.mdYAML frontmatter.
An enrolled runner reads its current allowed projects from Cloud, clones linked
repositories under workspace_root (default ~/detent-runner), and loads their
committed default-branch detent.yaml, WORKFLOW.md and AGENTS.md. It uses the
host's Git authentication. One project's missing repository binding or failed
clone is an instance setup failure; it does not stop valid projects or revoke
its Cloud grant. An empty allowed-project list runs nothing.
The runner file may set the Hub URL, identity file, organization, display name, capacity, heartbeat and lease timing, provider-capacity file, CPU/memory/IO pressure limits, startup pacing, port, log level, cache and workspace root:
apiVersion: detent/v1
kind: GlobalConfig
service_name: detent.runner
workspace_root: /absolute/path/to/detent-runner
client:
hub_url: https://cloud.detent.build/organizations/org_example
identity_file: /absolute/private/runner/identity.json
organization_id: org_example
display_name: Build host
capacity: 4
heartbeat_interval_seconds: 30
lease_ttl_seconds: 90
global:
max_concurrent_agents: 4
Enrolled runners reject unknown keys at startup, including nested machine-setting
keys. Errors name each unsupported key path. Removed keys include projects,
weight, priority, scheduling, fair_share, global.agents.model_selection,
client.native_projects and other local project policy defaults, even when their
values are malformed. Remove these keys before upgrading; the one-release
warning-and-ignore transition has ended. Before releasing this transition, the
release owner must confirm the compatibility release shipped, verify that no live
runner file still contains removed keys, and authorize the subsequent release.
An unchanged legacy file will fail startup and may leave its service restarting.
Cloud supplies project membership, rank and model selection; runners have no
file-defined project or model-selection fallback. Local instances store these
settings in SQLite.
Existing checkouts under workspace_root are matched by Git origin to
Cloud-allowed repositories. They do not supply workflow paths or policy.
Matching checkouts retain their project aliases and directories. With no repository
workspace root, Git's registered Detent issue worktrees preserve a distinct retained worktree root, branch and dirty source.
Explicit repository workspace roots remain authoritative. Multiple matching
checkouts or retained roots require the operator to select the retained source;
existing files remain untouched.
This page is the reference for machine and project configuration. Each project's workflow continues to come from its repository.
agent.no_progress_timeout_ms remains readable for older project files but is
ignored. Worker sessions use their absolute duration bound, and local validation
waits use the gate lock deadline.
Priority settings order ready work (INV-10). Stored project rank and
dispatch_priority_by_state, dispatch_priority_by_label, and
prioritize_unblockers choose the next eligible request. They never reserve
idle slots or cancel running workers. max_concurrent_agents and
max_concurrent_agents_by_state remain independent capacity ceilings.
Host Go caches
Workers inherit Go's host cache paths and operator environment settings. They retain per-attempt temporary directories. Startup removes legacy project caches under each configured workspace root; doctor and status report host cache paths and sizes.
global:
cache:
max_age: 48h
# max_bytes: 53687091200 # optional explicit 50 GiB size target
When omitted, max_bytes is 10% of the total capacity of the volume holding
GOCACHE, capped at 40 GiB (20 GiB fallback if capacity is unavailable). It is a
retained-size budget for the managed runner cache, not a hard filesystem limit
or a claim brake. Native Cloud
runners admit new work while each cache and workspace volume has enough free
space for its reserve: 10% of total volume capacity, capped at 40 GiB.
Insufficient free space or an unavailable storage observation reports zero
capacity for new work. Existing jobs keep their leases and recent cache files.
Free space does not change the cache target.
Doctor reports the fallback, last size eviction, and a target above 10%
of total volume capacity.
The existing workspace reaper expires build-cache entries older than max_age.
The effective age is at least 25 hours, preserving Go's last-day cache reuse and
its one-hour access-time granularity for native operator caches. When the
runner-owned cache exceeds max_bytes, that same cleanup waits under the
existing admission lock for protected consumers to finish, then reclaims
eligible entries oldest first under the exclusive consumer lock. New consumers
resume after cleanup. Metadata (trim.txt, README, and ownership locks) is
never removed. The module cache is reported and shared but is not trimmed.
Concurrent builds can grow the managed cache between sweeps and exceed the
budget while active. Unrelated host processes and native operator caches are
not controlled by managed cache reclamation.
GitHub completion CI policy
Omitting gate.required_status_checks retains aggregate CI evaluation. Explicit
gate.required_status_checks: [] uses the PR base branch's native required
checks. If the branch requires none, absent or pending optional CI does not
block promotion or merging. Reported failed CI always blocks. Native missing,
pending, and failed checks still block. A nonempty list retains the existing
configured-check behavior.
Issue session allowance
Code and rework share a fixed allowance of three sessions without a merged PR. The next code dispatch runs one read-only triage pass, publishes its explanation, and leaves the issue in Human Review. An operator move out of Human Review renews the allowance, as does a merged PR. Detent-instance moves, new PR heads, changed CI signatures, ordinary lane moves, and acknowledgements alone do not renew it; instance-attributed failures do not consume it. Completed, successful PR deliveries without a recorded error also do not consume it; waiting on the completion gate is not a failed delivery. The existing triage comment records the operator reset timestamp when comment updates are supported.
The former agent.auto_promote.rework_limit and
agent.auto_promote.no_progress_limit keys are retired and have no effect.
Remove them from existing configuration. There is no replacement tuning key.
See INV-3 for the consolidation and
historical-reader compatibility contract.
Session limits by complexity level
Model selection is configured in Cloud organization settings. Projects inherit
that default and may override it in their Cloud project settings. Runners receive
the effective selection from the Hub. global.agents.model_selection and
agents.model_selection in detent.yaml or legacy workflow frontmatter are
ignored with a compatibility warning for one release; the next release rejects
them. Remove those keys from runner and repository files.
Cloud complexity levels may include max_session_duration_ms and
max_session_tokens alongside model and effort. The selected level (stage
override, matching rule, then default) supplies these limits once at session
start. An omitted limit inherits agent's flat value; explicit 0 disables that
limit. Values must be non-negative. Selection disabled or an ineligible backend
uses the flat values. Per-issue detent-agent model and effort blocks retain their
existing behavior, including clamping effort to the stage's ceiling.
The running session keeps its resolved values across turns, checkpoints, and
fallbacks. Later label or configuration changes do not change its limits or reset
attempt counts or lifetime usage. Turn inactivity, no-progress limits, context
multipliers, and token exemption settings retain their existing behavior.
detent doctor reports each level's effective limits for the project, plus the
flat limits used by ineligible backends.
An issue without a detent-agent effort uses the default level (subject to
existing stage and role rules).
Terminal attempt recovery
Set recovery.terminal_attempt_retry_limit in detent.yaml or
detent.local.yaml to control recovery from consecutive terminal attempts
without a pushed work product or linked pull request:
recovery:
terminal_attempt_retry_limit: 0
The default is 3. The value preserves the existing consecutive-failure
threshold for values of 2 or greater; 1 explicitly permits one recovery
attempt, and 0 disables automatic recovery for a qualifying failure.
| Setting | Failure 1 | Failure 2 | Failure 3 | Recovery after parking |
|---|---|---|---|---|
0 |
Blocked | No automatic attempt | No automatic attempt | Operator reviews the failure and moves the issue to Todo |
1 |
Todo; one recovery attempt | Blocked | No attempt before recovery from parking | Existing breaker cooldown |
3 (default) |
Todo | Todo | Blocked | Existing breaker cooldown |
Thus 1 and 2 both park on the second qualifying failure. Higher values
park on that numbered failure. Negative values are rejected. This compatibility
rule keeps the existing default at two redispatches before parking.
The zero policy persists an operator-owned hold, including the latest failure evidence. It also enforces the hold from the current completion when saving the attempt fails or no durable attempt record is available. Restarting Detent or waiting for the breaker cooldown does not release that hold. If the tracker rejects the Blocked transition, Detent holds the issue locally and retries that transition during reconciliation without starting a worker. Changing the setting alone does not authorize release of an existing operator-owned hold. Retry counts are reconstructed from durable issue attempt history after restart; successful attempts and pushed work reset the sequence. Service-restart recovery remains resumable and does not consume this limit.
Durable provider capacity, GitHub REST capacity, and forge availability waits retain their existing handling and do not consume generic terminal retries. Transient overload failures remain qualifying terminal failures. Workspace preparation retains its separate fixed threshold of three failures. This policy is independent of automatic promotion, validators, and Rework limits.
detent doctor reports the effective setting, the qualifying failure on which
Detent parks, and whether parking requires external review or uses cooldown
recovery. Local overlays preserve an explicit zero.
Repository policy with Hub execution
The customer host resolves the project files and an administrator explicitly
approves the resulting descriptor. For native projects, that approved revision
stores the shared behavior and instructions consumed by every authorized runner.
Credentials, host paths, capacity, isolation hooks and backend routing stay local.
Repository compatibility projects retain their file-based approval semantics.
Use detent hub policy inspect --config /path/global.yaml --project orders to
review the resolved descriptor. Authenticated native inspection reads the approved
shared configuration; offline inspection resolves the local proposal.
Configuration precedence and authorization are applied in this order:
| Order | Layer | Effect |
|---|---|---|
| 1 | Parser defaults | Existing defaults and legacy compatibility are unchanged. |
| 2 | Project definition anchor | projects[].workflow selects the definition directory, including an external root. detent.yaml plus prompt-only WORKFLOW.md use schema 1; legacy frontmatter remains supported. Mixed structured authority is rejected. No competing checkout file is searched. |
| 3 | Machine overlays | Existing detent.local.yaml and WORKFLOW.local.md rules apply: mappings merge, explicit scalars/sequences replace, supported empty/false values clear, and local prose appends. |
| 4 | Host configuration | Existing flags/environment/global precedence, identity, paths, authorization filters, pool/backend limits, budgets and host ceilings remain enforced on customer infrastructure. Repository requirements cannot increase host permissions or capacity. |
| 5 | Hub policy approval | Native runners consume the approved shared behavior and instructions while retaining host configuration. Compatibility runners must resolve the approved effective files and local overrides. Both require an exact descriptor match; missing, stale or contradictory policy denies dispatch. |
| 6 | Run pin | Claims atomically pin the approved policy. The customer runner rechecks approval before dispatch, claim adoption and renewal, and checks its loaded workflow before credentials or workspace creation. |
Use an administrator-selected workflow_ref to read shared files from a trusted
Git commit. The loader resolves the ref once and reads shared files at that
commit; edits to the working branch do not change it. Shared behavior changes in
local overlays still need approval. An external definition is authorized by
reviewing its resolved content digest through the same approval command.
Approvals must be performed outside
the implementation worker; do not give workers the Hub administrator credential.
hub policy inspect prints schema 1, the source revision, source/configuration
digests, a content-derived policy_id, selected runner requirements and gate
metadata. Native descriptors also contain shared configuration, workflow prose,
commands and admission guidance. Their digests identify portable behavior rather
than host-specific source bytes or Git revisions. Host paths, credentials and
transport settings are excluded. Compatibility descriptors contain only digests
and bounded metadata; their underlying content stays on customer infrastructure.
After reviewing the files and descriptor, an administrator runs:
detent hub policy approve --config /path/global.yaml --project orders
The command reads DETENT_HUB_ADMIN_TOKEN (or --admin-token-env), resolves the
selected local definition and uploads its descriptor. Replacement requires
--expected-policy-id policy_... to prevent a stale approval from overwriting
another administrator's decision.
For repository-controlled native workflows, an enrolled runner automatically
applies definitions committed on the bound repository's default-branch history
through the same Hub approval transaction. It verifies the configured origin,
committed contents and Git ancestry before reporting provenance. Feature-branch
commits and uncommitted edits still require explicit administrator approval.
Other native shared behavior or instruction changes require one reviewed approval
for the project. Planning, validation and automatic promotion can be enabled
together; the existing workflow refresh applies the approved combination to
idle runners without copying files. Host configuration changes do not produce
competing native approvals. Existing native approvals without shared
configuration adopt this model through the same replacement approval path.
Compatibility projects still require new approval for effective file, permitted
local adjustment, environment or trusted source revision changes on each host.
Active leases retain their policy, and Hub refuses a replacement while they remain active. Finish or cancel those attempts, approve the new descriptor, and let native runners refresh the shared revision. Compatibility processes still require restart or the existing managed configuration application to adopt a different approved identity. Invalid or unapproved reloads retain the last-known-good configuration and report an actionable error. Resumed provider sessions must match the policy stored with their original attempt, including after Hub is disabled; missing or mismatched recovery identities are rejected. Automatic thread reuse falls back to a fresh attempt under the current policy. Standalone sessions that never had a Hub policy retain legacy recovery.
Administrators can revoke the current approval through the policy API. This immediately denies claims, lease renewal and new run events. Worker heartbeat checks observe the revocation and stop work through existing lease-loss handling; release remains available. Historical revisions and lease pins are retained.
Plan-review stops, gate kinds, human approval, automatic-promotion opt-outs, CI/check requirements, validators, security audits and merge methods remain project-specific. Two repositories sharing a runner retain separate approvals and gates. Hub approval is not a human plan/PR approval. GitHub's repository automatic-merge setting remains owned by that repository; onboarding inspection reports it without changing it. Host restrictions and revoked authority win over an approved policy or a Hub allowance. Hub has no browser setting that silently relaxes repository policy.
Effective policy appears in project settings, work-attempt receipts, health
workflow provenance and detent doctor output. Policy mismatches leave work
waiting without creating an execution attempt or consuming execution retries.
Named runner requirements
Declare profiles in the existing shared or legacy configuration and select one
with runners.profile:
runners:
profile: build
profiles:
build:
required_tags: [linux, go]
machine_id: machine_0123456789abcdef
runner_id: runner_0123456789abcdef
Profiles and tags use ASCII letters/digits with -, _ and ., up to 64
characters. Tags are trimmed, lowercased, sorted and deduplicated; profile names
must already be lowercase. At most 32 profiles and 32 tags per profile are
accepted. Selectors are optional stable IDs with machine_ or runner_ prefixes;
display names are not selectors. Every required tag and every explicit selector
must match. Unknown selectors never widen to another host. An empty profile
selection has no extra routing requirements. Explicit empty lists and selector
strings in local overlays retain their existing clearing semantics and require
approval as part of the effective definition.
Profiles declare requirements, never enrollment or capabilities. Machine
registration still happens on an authorized physical host. The Hub checks
machine pins and matches runner IDs only against authenticated enrolled runner
identities. Legacy worker tokens cannot assert an enrolled runner ID. Required
tags return selector_no_match until the authorized tag-placement deliverable
#2185 is available. Self-reported machine capabilities never satisfy privileged
tags. See runner enrollment for the separate host authorization path.
Host configuration
At startup, Detent resolves global.yaml in this order. The first matching rule wins.
| Order | Rule | Path |
|---|---|---|
| 1 | --config <path> |
Direct file path from the CLI flag |
| 2 | CONFIG=<file> |
Direct file path from the environment |
| 3 | CONFIG_HOME=<dir> |
<dir>/global.yaml |
| 4 | os.UserConfigDir() |
<config-dir>/detent/global.yaml |
| 5 | Legacy home config | ~/.detent/global.yaml |
os.UserConfigDir() maps to %AppData%\detent\global.yaml on Windows, ~/Library/Application Support/detent/global.yaml on macOS, and ~/.config/detent/global.yaml on Linux while honoring XDG_CONFIG_HOME.
DETENT_CONFIG and DETENT_HOME remain deprecated fallbacks for one release. Detent uses CONFIG_HOME instead of HOME because HOME is standard process state, not Detent configuration.
Use detent config path to print the resolved config path and the rule that selected it.
Runtime settings resolve in this order: explicit flag, environment variable,
global.yaml, then built-in default.
| Setting | Flag | Environment | global.yaml key |
Default |
|---|---|---|---|---|
| Environment | --env |
ENV, then DETENT_ENV |
env |
prod |
| Log level | --log-level |
LOG_LEVEL, then DETENT_LOG_LEVEL |
log_level |
info |
| Log max size | LOG_MAX_SIZE_BYTES, then DETENT_LOG_MAX_SIZE_BYTES |
log_max_size_bytes |
52428800 |
|
| Log backups | LOG_MAX_BACKUPS, then DETENT_LOG_MAX_BACKUPS |
log_max_backups |
5 |
|
| GitHub token | GITHUB_TOKEN |
github_token |
required for GitHub projects | |
| API token | DETENT_API_TOKEN |
api_token |
open on loopback, fail closed on non-loopback | |
| Loopback peer read trust | trust_loopback_peer_read |
false |
||
| Hub scheduler | client.hub_url |
disabled | ||
| Hub worker token | DETENT_HUB_TOKEN |
client.token_env names the environment variable |
required when Hub scheduling is enabled | |
| tmux window status | $TMUX detection |
ops.tmux_window_status |
enabled inside tmux | |
| Web port | --port |
PORT |
port |
4000 |
| Instance name | instance_name |
short hostname | ||
| Service name | service_name |
detent |
||
| Health webhook | notifications.health.webhook.url |
disabled | ||
| Health notification debounce | notifications.health.debounce_seconds |
300 |
||
| Health webhook timeout | notifications.health.webhook.timeout_ms |
5000 |
||
| Automatic update checks | update.auto_check_enabled |
true for Hub runners, otherwise false |
||
| Update check interval | update.check_interval_hours |
Hub heartbeat interval for Hub runners, otherwise 6 hours |
||
| Automatic update apply when idle | update.auto_apply_enabled |
true for Hub runners, otherwise false |
||
| Maximum automatic update deferral | update.max_deferral_hours |
6 |
Automatic apply waits for an idle runtime until update.max_deferral_hours
elapses, then pauses new dispatches and lets in-flight attempts finish within
their configured session ceilings before applying and restarting. Add
[detent-critical] to a GitHub release body to bypass the idle wait and start
that drain immediately.
detent cloud serve and detent hub serve read no global.yaml; they resolve
the log level from --log-level, then LOG_LEVEL, then DETENT_LOG_LEVEL,
default to info, reject any value other than debug, info, warn or
error, and write JSON logs to stderr for journald.
The web host resolves from --host, then the first registered workflow's
server.host, then the built-in 127.0.0.1 default. It is not a top-level
global.yaml key.
Host pressure admission
On Linux, Detent refreshes Pressure Stall Information (PSI) at admission time, subject to each signal's configured poll interval. Pressure above any configured threshold holds new dispatches; it does not cancel or reap agents that are already running.
global:
memory:
max_agent_rss_bytes: 8589934592
pressure_some_avg60_threshold: 10
poll_interval_ms: 1000
io:
pressure_full_avg10_threshold: 5
degraded_max_concurrent_agents: 0
poll_interval_ms: 1000
cpu:
pressure_some_avg10_threshold: 80
degraded_max_concurrent_agents: 0
poll_interval_ms: 1000
go_build_budget: 0
go_build_budget is the host-wide number of Go compile, link, asm, cgo, and vet processes that all worker attempts may run at once. The default of 0 uses the host's CPU count. For workers whose workspace root has a go.mod, Detent sets GOFLAGS=-toolexec=<detent go budget wrapper>, so every go build, go test, go vet, and go install a worker runs, including commands the agent types itself, waits for a free slot before each tool process starts. The same environment sets -p and GOMAXPROCS to a quarter of the budget (at least 1) so each go command's package parallelism and test binaries stay within the host's share. Flags the host environment already sets in GOFLAGS are kept, and an explicit -p or -toolexec wins. The budget only queues tool processes; it never fails, parks, or cancels work, and it runs unqueued if its slot directory cannot be prepared. Workspaces without a go.mod keep their environment unchanged. The shared GOCACHE is unchanged and build and test cache hits are preserved. Changing this value requires a restart.
degraded_max_concurrent_agents is an opt-in host-wide progress floor for IO
and CPU pressure. Its default of 0 preserves the conservative hard stop. Set
it to 1 to allow one worker across all named pools while that signal remains
constrained. If IO and CPU pressure are active together, the lower configured
floor wins. The pressure ceiling only blocks new admissions; it never preempts
running workers, and pool, project/state, worker-host, startup pacing, and
backend limits can reduce capacity further.
A guard engages only above its configured threshold. Once engaged, IO and CPU
guards stay constrained until the measured average falls to 80% of the trigger
threshold, which prevents dispatch from flapping when PSI hovers around the
boundary. For example, an IO threshold of 5 engages above 5 and recovers at
or below 4.
The defaults shown above are starting points, not universal safe values. With
the default degraded capacity of 0, the sample IO threshold of 5 is a
fleet-wide hard stop whenever IO full avg10 stays above 5%, including when an
unrelated host process creates the pressure. IO full is the strongest build
host signal because it measures time when every non-idle task is stalled on IO.
CPU uses some, because system-level CPU full is defined to report zero.
Memory some remains useful for reclaim and swap pressure, but a compile-heavy
host can be IO- or CPU-saturated while memory PSI stays at zero. See the
Linux PSI interface
for the kernel definitions.
Treat configured pool capacity as the hard ceiling and use PSI as the dynamic
admission brake. Before enabling a low threshold, observe /proc/pressure/io
and /proc/pressure/cpu during representative Detent and unrelated compile,
test, browser, database, and media workloads. Calibrate the trigger above the
host's acceptable sustained baseline, then lower it if the interactive host
becomes unresponsive before the guard engages. Raising the hard ceiling based
only on free memory can overload a build host.
The ten-second IO and CPU windows respond faster than the sixty-second memory
window. Keep global.startup.jitter_seconds and max_spawn_per_second
conservative enough for those averages to react during service startup; PSI
then constrains and restores admission as contention changes. On systems without PSI,
or when a pressure file cannot be read, Detent fails open and reports the
unsupported or unavailable signal rather than inventing a zero reading.
detent state, /api/v1/state, and /health expose the current values,
thresholds, read errors, configured degraded capacity, effective pressure
capacity, constraint start time and duration, and whether each signal is
holding dispatch under memory_pressure, io_pressure, and cpu_pressure.
Durable scheduler decisions include pressure capacity, usage, availability,
reason, and duration. Work-attempt capacity snapshots include the active
pressure capacity, reason, and duration.
When Detent starts inside tmux, it renames the current window to a compact
running, ready, waiting, and blocked count summary. It restores the original
name on clean shutdown. Set ops.tmux_window_status: false to disable this
behavior; Detent never invokes tmux when $TMUX is absent.
Use github_token: gh in global.yaml to resolve the token from
gh auth token at startup. Literal token values also work but should not be
committed. github_token: gh-auth, ${gh auth token}, and
$(gh auth token) are accepted aliases. If neither GITHUB_TOKEN nor
github_token is set, Detent falls back to existing per-workflow
tracker.api_key handling.
Set trust_loopback_peer_read: true to grant tokenless read-scope API access
to direct loopback TCP peers even when Detent binds a non-loopback address or
has an api_token. The setting hot-reloads and applies only to GET routes;
admin routes and mutations still require credentials. Authentication uses only
the request's direct peer address and never trusts forwarded-client headers.
Do not enable this setting when a reverse proxy runs on the same host: every
request relayed by that proxy has a loopback direct peer, including requests
that originally came from remote clients.
Use instance_name to distinguish browser tabs and the dashboard navbar when
several Detent instances are open at once. Detent resolves the display name
from the first non-empty value in this order: top-level instance_name in
global.yaml, global.identity.name, the short hostname, then empty. Names
are trimmed, must be a single line, and are capped at 40 characters in the web
UI.
service_name names the background service: the launchd job
com.digitaldrywood.NAME and the systemd user unit NAME.service. It defaults
to detent; detent hub runner register writes service_name: detent.runner.
Give each configuration on one host its own name. Names use lowercase letters,
digits, dots and hyphens.
A project listed under client.native_projects whose committed detent.yaml
uses the GitHub tracker (github or github_local) uses the Hub instead
(tracker.kind: hub_native), so a runner host can use a repository checkout
as it is. Any other tracker kind is treated as a deliberate local choice and
kept.
Configure client.hub_url to move candidate discovery and claiming to a Detent
Hub. The machine registers its identity, project and pool capabilities,
capacity, operating system, architecture, and binary version. It then
heartbeats while polling the Hub and renewing fenced work leases. The token is
read from the environment variable named by client.token_env in legacy mode;
the token is never stored in the configuration file. For scoped native runners,
use client.identity_file instead: an absolute path to the private host-generated
identity created by detent hub runner init and enrolled with detent hub runner enroll. The client renews this credential automatically. Its organization,
machine ID and explicit native projects must match enrollment. See
runner onboarding and migration.
Enrolled native runners can opt into provider-aware dispatch with
client.provider_capacity_file, an absolute path to a bounded JSON report file.
The runner reads it on heartbeats and again before starting provider work. See
provider capacity reports for its safe
schema, shared-account declarations and reservation semantics. This setting does
not change backend credentials, model routes, effort defaults or fallback policy.
client:
hub_url: https://hub.example.com
token_env: DETENT_HUB_TOKEN
machine_id: build-mac-01
display_name: Build Mac 01
capacity: 4
heartbeat_interval_seconds: 30
lease_ttl_seconds: 90
request_timeout_ms: 10000
In legacy mode, machine_id defaults to the configured global identity, instance
name, or hostname. Enrolled mode uses the immutable host-generated machine ID.
capacity defaults to global.max_concurrent_agents. The heartbeat
interval must be shorter than the lease TTL. Hub outages mark project refresh
health degraded and back off with the normal scheduling loop; they do not
consume issue retry, no-progress, or work-failure budgets. GitHub remains in
use for event ingestion, reconciliation, write-back, and fresh safety checks,
but the scheduling claim cycle does not depend on a GitHub read.
Native issue projects use tracker.kind: hub_native in their repository
workflow configuration. Enrolled runners obtain the stable project IDs and
organization through their identity and current Cloud grants. No local
client.native_projects mapping is needed. Native issue creation, full-body
refresh, discussion, history and scheduling use the Hub v2 API. There is no
fallback scheduler. The Hub API
documents project provisioning, workflow states and typed operations.
A native project reuses the repository WORKFLOW.md as its prompt. Detent
appends a native completion contract to every native run prompt that overrides
tracker-specific steps in that file: the agent commits on the attempt branch,
never pushes or opens pull requests on the forge, never runs gh or calls the
GitHub API, and does not post Workpad or issue comments or change issue state.
The runner records the Change Request from those commits. Native projects make
no GitHub REST or GraphQL calls by default: workers do not receive the instance
GitHub credential, and checkpoints do not look up or open pull requests. No
separate key turns this on; an explicit worker.github_token is the only opt-in
for worker GitHub access. The
contract is added at render time, so it does not change the approved policy
digest. detent doctor (check Project <id> native workflow instructions) and
detent hub policy inspect warn when a native project's WORKFLOW.md still
mentions GitHub-only steps such as Codex Workpad, a gh command,
pull request, detent-status or the GitHub API.
Health notifications deliver fleet and project needs-attention transitions to
one generic webhook. They are disabled when
notifications.health.webhook.url is absent, preserving the silent default.
Configure the host-wide channel in global.yaml:
notifications:
health:
debounce_seconds: 300
webhook:
url: https://alerts.example.com/detent
headers:
Authorization: Bearer replace-me
timeout_ms: 5000
The five-minute default debounce applies independently to entry and recovery.
Project identities are stable per project and cause, currently
ci_unavailable and dispatch_stall; a separate fleet identity represents
the aggregate transition while any project cause remains active. A flap that
clears before the debounce expires emits neither entry nor recovery. Once an
entry is emitted, exactly one recovery is emitted after the identity remains
healthy for the debounce window.
Webhook payloads use event name detent.health.transition and include a stable
event ID, host and instance names, scope, optional project ID, entered state,
causes, optional wait reasons, and entry timestamps. Delivery state and pending
events are stored in the runtime SQLite database before sending, so a restart
does not re-fire active conditions or discard unsent transitions. Failed sends
retry at most five times with exponential backoff starting at 30 seconds and
capped at 15 minutes. Every failure is logged and appears in
health_notification_failures on /health; detent doctor reports both
retrying and exhausted deliveries. Notification configuration changes require
a restart. Treat configured headers as secrets and keep host-specific values
out of checked-in project configuration.
Automatic update checks and apply are enabled by default when client.hub_url
is configured. These runners check the Hub's running version through its
authenticated native capabilities response at the configured heartbeat interval,
unless update.check_interval_hours is set. They fetch that exact published
release rather than GitHub latest. A runner ahead of the Hub stays on its
installed version; an unreachable Hub, missing version or unpublished target
does not trigger a fallback update. Explicit auto_check_enabled: false or
auto_apply_enabled: false settings opt out and survive configuration writes.
Detent reports the last check, available or applied version, and next check on
/health. detent doctor surfaces the last and next check times and reports
whether the host is opted in. Automatic apply only replaces release-installer
binaries; other install sources remain notification-only. A successful apply
uses the normal graceful drain path, then re-executes the replaced binary on
POSIX systems or exits cleanly for an external supervisor to restart it on
other platforms.
detent doctor prints the resolved runtime values and their sources, with the
GitHub token redacted.
Choose a starting point
The root detent.yaml and WORKFLOW.md configure the Detent project itself.
They are not templates for other projects. Use the documentation samples below
when configuring another repository.
docs/templatescontains paireddetent.*.yamlandWORKFLOW.*.mdpresets for supported tracker and deliverable setups.config.example.yamlis the smallest working project config.config.annotated.yamlis a realistic GitHub setup with explanations beside each deliberate choice.config.reference.yamlcontains every supported project key. It is fully commented so you can copy only what you need.
Keep detent.yaml portable and checked in. Put machine-specific paths or
secrets in detent.local.yaml, a credential reference, or host configuration.
The local file overlays the shared file recursively and is not a second,
independent configuration.
How the subsystems fit together
Identity, tracker, and polling
identity names the Detent persona that owns work. tracker connects that
persona to its work source; native projects use tracker.kind: hub_native,
which keeps issues in the Hub. The tracker declares workflow states in one
ordered tracker.lanes list; state maps, transition automation, admission,
planning, stop behavior, and routing all refer back to those names. polling controls how often Detent refreshes the
tracker when no event gives it a fresher signal. Its
refresh_failure_threshold controls how many consecutive failures after a
successful refresh promote project and fleet health to needs_attention; a
project whose first refresh fails needs attention immediately.
Each lane has a name and a role: active means dispatchable, holding
means non-dispatchable and non-terminal, and terminal means finished. File
order is the order on Cloud boards and lists, lane pickers, and approved
repository workflow policies.
tracker:
lanes:
- {name: Backlog, role: holding}
- {name: Todo, role: active}
- {name: In Progress, role: active}
- {name: Rework, role: active}
- {name: Merging, role: active}
- {name: Blocked, role: holding}
- {name: Human Review, role: holding}
- {name: Done, role: terminal}
- {name: Cancelled, role: terminal}
tracker.observed_states, tracker.active_states, and
tracker.terminal_states are rejected in detent.yaml, legacy WORKFLOW.md
frontmatter, and Cloud Workflow settings, including empty and null values.
Use tracker.lanes for all new and updated workflow configuration.
Run detent config migrate detent.yaml (or detent config migrate WORKFLOW.md
for legacy frontmatter) to rewrite the file in place. The command preserves
lane semantics, comments outside the replaced lists, and the Markdown prompt;
running it again leaves the file unchanged. An explicit local lanes list
replaces the shared lane list in full. Cloud-authored Markdown is
migrated automatically by the Hub, preserving each project's stored lane order
and approval provenance. Historical policies and approvals remain readable.
dependency_auto_unblock, blocked_recovery, and blocker_auto_promote are
opt-in tracker automations: the first advances work whose dependencies cleared,
the second revives agent-recoverable PR maintenance, and the third promotes
blockers when they enter configured states.
GitHub and Linear trackers also receive a default Statuspage base URL through
tracker.status_page_url; set that key to override the provider endpoint.
Detent follows redirects and polls the unauthenticated Statuspage summary and
unresolved-incident feeds at a low-frequency baseline, increasing freshness
only while a Detent-observed tracker_unavailable condition is active. These
feeds decorate health output only: malformed, slow, or unreachable status
pages never create a condition or influence dispatch and recovery. Statuspage
incident webhooks are a possible future optimization, not part of the current
polling integration.
The tracker setting github_rest_min_remaining_reserve is an absolute reserve
for the REST core resource (default 1000). It does not reserve requests in
smaller resources such as search; those resources still enforce GitHub's
primary and secondary rate limits and shared backoff. Recovery probes,
reserve diagnostics, and scheduler lookup gates use the same resource rule.
The aggregate REST quota snapshot retains core capacity; resource-specific
budgets continue to report search usage separately. Responses without a resource
name retain the core reserve for compatibility. No configuration migration or
new setting is required; the worker's separate core-budget policy is unchanged.
Workspace, deliverable, and worker placement
workspace controls isolation, source and output roots, branch creation, cache
sharing, and cleanup. deliverable selects pull requests or file artifacts and
their review destination. worker distributes sessions across optional SSH
hosts; per-state and global concurrency limits remain under agent.
SSH workers run the installed detent binary on each selected host over an
SSH stdio channel. Workspace setup, hooks, provider processes, gates, and Git
pushes execute there; session records, Workpads, lane decisions, and merge
coordination stay with the orchestrator. Hosts use normal SSH configuration and
host-key verification, with noninteractive authentication and no agent forwarding.
worker:
# Per runner host; omitted prices inherit Sprites public rates.
compute_rates:
local:
cpu_hour_usd: 0.07
memory_gb_hour_usd: 0.04375
ssh_hosts: [example-laptop, example-build-host, local]
host_selection: preference
max_concurrent_agents_per_host: 2
host_caps:
example-laptop: 2
example-build-host: 6
local: 1
Sandboxed workers can opt into localhost servers and connections for HTTP tests
with worker.allow_local_binding. It defaults to false. Set the instance default
in the global config:
global:
worker:
allow_local_binding: true
Override that default in a project's detent.yaml or machine-local
detent.local.yaml:
worker:
allow_local_binding: false
An omitted project value inherits the instance default; explicit false disables it even when the instance default is true. The effective grant participates in Cloud policy approval. It keeps the sandbox tier and file restrictions, but permits connections to other localhost services as well as temporary test servers. Codex also skips its proxy's private-network destination check with this grant; proxy domain rules still apply. Restricted Codex turns keep networking disabled.
A project can grant additional exact DNS hosts through
worker.extra_network_domains in detent.yaml or detent.local.yaml:
worker:
extra_network_domains:
- fonts.googleapis.com
- fonts.gstatic.com
The list adds to Detent's dependency/provider hosts. It defaults to empty and participates in Cloud policy approval. Wildcards, URLs, IP addresses, ports, duplicates and uppercase hosts are rejected. Filesystem restrictions and the sandbox tier remain in effect; restricted Codex turns keep networking disabled.
preference fills hosts in list order to their project-specific cap. The default
least_loaded selects the least busy available host and retains retry affinity
when that host has capacity. host_caps overrides the fallback
max_concurrent_agents_per_host for individual configured hosts. Include local
where local execution belongs in the order; an empty list means local execution.
The existing project and instance agent limits still apply.
Install matching Detent binaries, Git, gh, and the configured provider on every
remote host. Provider subscription authentication must already be available there.
The noninteractive SSH environment must provide a writable TMPDIR, TMP, or
TEMP; workers never fall back to host scratch space. Paths below the
orchestrator's home directory are mapped below the remote user's home directory;
other absolute paths must exist on that host. Hook references to the home path
are mapped too. Provision the source checkout there, or allow Detent to clone
its HTTPS origin into the mapped source root on first use.
Detent checks SSH reachability and the worker protocol before dispatch, caches
the result for 15 seconds, and skips unavailable hosts. A lost channel cancels
the remote lifecycle and uses the existing instance retry path. A retry on a
different host starts a fresh provider session. Existing workspace reaping and
reconciliation also visit configured remote hosts. GitHub credentials cross only
the encrypted channel and use the same private temporary GH_CONFIG_DIR as
local workers; they are removed when the worker exits and never written to the
source checkout or persistent Git configuration.
Remote workers support GitHub, Linear, and hub_native trackers. Native runs
capture artifacts and attempt diffs on the selected host through SSH; upload
journals, credentials, lease fencing, Change Request publication, landing reports,
and provider reservations remain central. Interrupted transfers retain their
journals for existing recovery. Both endpoints must support SSH protocol version 2.
GitHub-capable workers never inherit GITHUB_TOKEN, GH_TOKEN, their
enterprise variants, or the host's GitHub CLI configuration. When omitted or
empty, worker.github_token defaults to the resolved top-level github_token
(including github_token: gh), except for tracker.kind: hub_native
projects, which never inherit the instance credential. An explicit project worker
credential overrides that default; with neither credential configured, the worker
policy is disabled.
For native landing pushes, HTTPS uses the runner host's GitHub CLI login when
no worker token is handed off; SSH uses the host's SSH key. An explicitly handed
off worker token remains the Git credential for HTTPS operations in that worker.
worker.github_token controls agent GitHub access; it is not required for native
landing. Agent GitHub CLI calls retain their private GH_CONFIG_DIR in both cases.
detent doctor includes a named landing push credential check that runs a
noninteractive dry run without changing remote refs.
Set the worker override to gh to resolve gh auth token in the Detent process
and copy only the resolved token into the worker's isolated GH_CONFIG_DIR. An environment
reference remains available when permission or revocation isolation is useful:
worker:
github_token: gh
github_token_resolution_timeout_ms: 15000
github_rest_min_remaining_reserve: 1250
github_rest_poll_interval_ms: 60000
The token-resolution timeout applies to each gh auth token invocation. Detent
tries the command up to three times with short backoff before recording a
same-attempt infrastructure wait; these failures do not consume issue attempt
or failure-breaker budgets. Increase the timeout when the host credential store
can legitimately take longer than 15 seconds.
After resolving gh, Detent classifies an exact orchestrator token or any token
that resolves to the same GitHub user as shared_budget: GitHub's primary REST
limit is per user, so a second token changes permissions and revocation without
creating another rate-limit pool. Shared-pool telemetry uses
consumer: shared_pool and reports usage attribution as indeterminate rather
than assigning the pool's global usage to workers. A different GitHub user or
App installation is distinct_principal, the true rate-limit-isolation mode.
The default worker reserve is 1250, above the default orchestrator dispatch floor of 1000. Shared-mode workers therefore brake first so dispatch can continue. Detent probes the worker credential at launch and no more often than the configured interval, and stops or refuses a worker at its reserve. Keep literal tokens out of checked-in project files.
workpad.structured_only requires machine-readable workpad status instead of
accepting legacy narrative signals. GitHub native blocked-by relations and
recognized current issue-body dependency lines form one blocker list. Native
metadata wins for duplicate references; an empty native list does not discard
body declarations. Fenced examples never add blockers. Historical comments are
diagnostic only, with unmatched references reported as ignored in issue
explanations. Unsupported repositories use body declarations alone. Failed
blocker-state lookups propagate errors rather than returning dispatch candidates.
The existing dependencies.source values (merged and native_only) are
deprecated compatibility aliases for this same behavior; neither disables
current body declarations. Remove a body declaration to remove its blocker
(and remove the native relation as well if one exists).
Agents, backends, and routing
agent is the orchestration policy: concurrency, turn and session limits,
retry brakes, spend limits, shutdown, dispatch priority, automatic promotion,
state-specific instructions, learned context, skill drafts, and follow-up
work. agents.backends defines pluggable Codex, Claude Code, or Pi processes, while
agents.routes selects a backend and model by role or issue selector.
codex is the legacy/default Codex backend configuration and remains the
fallback when agents.backends is empty. A Codex backend inherits omitted
values from it. Claude Code backend options are interpreted only when the
backend kind is claude_code.
Pi uses kind: pi_agent, protocol: rpc, and command: pi by default. The
backend provider and existing routes select its provider and exact model ID.
Pi requires native-trusted isolation; sandboxed/read-only turns, resume,
Detent dynamic tools and persisted history hydration are unsupported in this
initial integration. See Pi backend configuration and limitations
for a complete route example, authentication and an opt-in smoke test.
Implementation workers on automatically owned Git branches request a bounded
recovery checkpoint when a duration, turn, or no-progress limit stops them.
agent.checkpoint_interval_ms also enables periodic checkpoints; 0 disables
the periodic trigger. Each checkpoint allows up to two minutes for a read-only
worker to select reviewed issue files and for Detent to commit and publish them
with lease and remote checks. Periodic checkpoints retain the session deadline
and accumulated budget usage. Identical work does not create new commits.
Checkpoint publication may associate a draft PR and never satisfies completion,
review, or CI gates. If delivery is unavailable, the owned workspace remains
recoverable. Its Git metadata contains detent-checkpoint.json, written before
worker execution, so an interrupted or killed worker does not imply that a
checkpoint epilogue ran. Human branches and backends without the handshake
continue to rely on local workspace recovery.
agent.auto_promote and gate work together. Auto-promotion decides when an
item may leave its source state; the gate decides whether CI, automated review,
human approval, a validator, or an artifact status is sufficient. plan
optionally inserts a plan-review stop before implementation.
The validator records evidence for each acceptance criterion on the immutable
Change round. gate.validator.unverified_criteria defaults to rework: any gap
returns the work for evidence, with the criterion named. Set it to
pass-with-disclosure to allow a pass while retaining the unverified list for
review. Minimum score and blocked finding severities still apply. Criteria come
from the issue contract's Acceptance criteria section, or its first required
section when a project uses a custom contract without that heading.
Operations, budgets, and observability
active_hours optionally limits new agent dispatches to recurring weekday and
wall-clock spans in a required IANA timezone. Use window values such as
Mon-Fri 22:00-06:00; ranges that end before their start wrap past
midnight, and 00:00-24:00 covers a full day. A project entry in global.yaml
overrides global.active_hours, and either host-local value overrides the
project's checked-in setting. Window close drains existing agents instead of
stopping them.
Detent evaluates membership on each scheduler tick, preserving local
wall-clock hours through daylight-saving changes and restarts. Spring forward
can shorten a wrapping window by one hour, while fall back can lengthen it by
one hour. detent doctor previews opening and closing times in both the named
timezone and UTC. A temporary detent resume <id> --for <duration> override
admits work outside the window until its persisted expiry; manual pause remains
the stronger, independent gate.
observability controls dashboard refresh, efficiency anomaly thresholds, and
optional OTLP export. budget is the project-wide spend policy;
agent.budget can further constrain agent sessions. release enables
release-readiness automation after merged work accumulates.
Release automation requires tracker.kind: github, tracker.repository: owner/repository,
and release.enabled: true. It is unavailable for hub_native with either
plain git delivery (deliverable.github_pull_request: false, the default) or
GitHub pull request delivery (deliverable.github_pull_request: true). Config
validation rejects release.enabled: true for native projects, including the
release-unavailable warning in the returned error so creation and reload failures
surface it. Leave release.enabled: false when migrating to hub_native.
Opting into GitHub pull requests does not enable release automation.
observability.stranded_active_threshold_seconds controls when Health and
detent doctor report an issue that remains in In Progress without a live
worker and when Detent recovers it automatically. Issues with an open pull
request or recoverable workspace work move to Rework; issues confirmed to
have no recovery artifacts move to Todo. Detent holds the issue in place
when artifact evidence is unavailable. The default is 600 seconds, which
tolerates normal gaps between a completed session and prompt re-dispatch.
hooks run lifecycle commands around worktree creation, agent execution, and
cleanup. Treat them as trusted code: they run with the Detent process
permissions and should be short, deterministic, and safe to retry.
hooks.runner_setup names a repository-relative script for project tools on
a registered runner. It runs from the project's source checkout during runner
preparation, before any work is claimed, and is checked before subsequent
dispatch. It uses hooks.shell and hooks.timeout_ms (60 seconds by default;
increase this for installers). The script need not have executable permission.
hooks:
runner_setup: scripts/runner-setup.sh
shell: bash
timeout_ms: 300000
Each runner records the successful SHA-256 of the script content, shell and
source checkout path in its local store, scoped to its runner identity and
project. When the checkout is a Git clone, the runner also writes the hash to
.git/detent-runner-setup inside it. Unchanged setup does not run for later
attempts or after restarting with the same store. Changing the script, shell
or checkout path, or recreating the checkout, runs setup again before the next
dispatch. With
workflow_ref, the script comes from the same Git revision as the project's
configuration; otherwise it comes from the source checkout, including local
edits. Paths must remain inside the repository. Keep setup self-contained:
changes to files sourced by the script do not change its recorded hash.
Setup is trusted host code. Make it idempotent, non-interactive, and free of secrets; the runner closes standard input and inherits its host environment, including native tool caches and supplied scratch directories. Install CLIs into a directory already on the runner's PATH. Shell exports do not persist into later agent turns. Setup failures are instance-owned for that project on that runner, appear in runner logs and the project's local-check API report, and prevent claims and dispatch without consuming an issue failure allowance. The existing project polling retries setup until it succeeds. Other projects and runners remain eligible.
Use workspace hooks for worktree or attempt preparation. Move project tool
installation from hooks.after_create, hooks.before_run, or Sprite pool
customer bootstrap into this script. Pool bootstrap is reserved for
prerequisites that cannot live in a readable repository file, such as fetching
credentials through a customer's secret service or obtaining the checkout.
Intake, retrospectives, routines, and admission
intake.sources, retro, routines, and backlog_admission are scheduled
features of repository trackers. tracker.kind: hub_native rejects
intake.sources and routines, retro requires a github or memory
tracker, and backlog_admission needs candidate selectors that native trackers
do not provide. Native cutover refuses a workflow that still enables any of
them.
Fleet staleness warnings
observability.staleness detects work items that exceed lane-specific aging
thresholds, non-empty dispatch or merge queues that stop advancing, and
repeated identical scheduler decisions. Lane residency starts at the most
recent entry. Repeated entries can still raise a cumulative warning when their
residency exceeds the lane threshold within
observability.staleness.lane_reentry_window_hours. Human-gate lanes emit one
reminder at their threshold, then remain quiet until the lane changes or
observability.staleness.human_gate_rearm_hours passes. Sticky breaker and
operator parks with a recorded cause are excluded from lane aging because the
Blocked lane already carries their operator signal. A park_cause_stale
warning replaces lane aging when recorded evidence is cleared or can no longer
be verified.
Operator-facing warnings use the same authorization selector as board and
runtime rows. Item warnings outside that selector are excluded before they are
logged, delivered, or counted. Project-level liveness warnings are computed
from authorized dispatch and merge queues. Repeated decisions about the
authorization boundary itself, including authorization_selector_declined,
remain visible even when the affected item is out of scope because the selector
decision is the condition the operator must correct.
A Blocked item observed from tracker state without a recorded blocker cause is
classified as blocked, cause unrecorded. The card keeps that classification
beside its lane provenance, and an eventual lane-aging warning names the missing
cause and the remedy instead of presenting an unexplained generic Blocked age.
The repeated-decision detector considers only current, non-terminal items.
Closed and merged items and items in a configured terminal state are excluded.
observability.staleness.repeated_decision_benign_reasons lists scheduler
reasons that represent healthy waits rather than operator-actionable stalls.
Its defaults cover active workers, dependency waits, global-capacity waits, and
GitHub REST pacing. Set the list explicitly to replace those defaults for a
project. Values must exactly match a scheduler-emitted reason. Configuration
validation reports the configured value and the emitted reason set when they do
not match; for example, unauthorized does not match
authorization_selector_declined. This validation is also surfaced by
detent doctor during config resolution.
The detector evaluates decisions inside
observability.staleness.repeated_window_hours, but the orchestrator retains
only the 500 most recent scheduler decisions. Its effective history is
therefore whichever is smaller: the configured time window or the retained
500-decision sample.
Warnings appear in the dashboard, /health, and detent doctor. Configure
observability.staleness.webhook.url to push each newly active warning as a
detent.staleness.warning JSON event. The payload includes a stable warning ID,
the affected project or item, the reason, and age and threshold values in
seconds. A delivered warning is not sent again on every scheduler tick. A
dashboard dismissal persists until the warning condition changes, such as a
new lane entry or a new scheduler cause.
Unstarted GitHub checks
tracker.github_unstarted_check_threshold_seconds controls when a GitHub check
that remains queued without a started_at timestamp appears as unstarted. The
15-minute default is intentionally non-zero because a cold or busy runner pool
can legitimately take several minutes to pick up a job; setting the threshold
too low would turn ordinary queueing into an operator-facing signal.
Codex deliverable elicitation
codex.deliverable_elicitation_allowlist declares exact MCP mutation tuples
that an unattended Codex worker may confirm for its configured deliverable. The
same field is available under agents.backends[].options for a routed Codex
backend. It defaults to an empty list. Each entry requires server, tool, and
repository; server-only entries are invalid.
codex:
approval_policy: never
deliverable_elicitation_allowlist:
- server: codex_apps
tool: github.create_pull_request
repository: digitaldrywood/detent
- server: codex_apps
tool: github.update_pull_request
repository: digitaldrywood/detent
- server: codex_apps
tool: github.add_comment_to_issue
repository: digitaldrywood/detent
- server: codex_apps
tool: github.update_issue_comment
repository: digitaldrywood/detent
- server: codex_apps
tool: github.update_issue
repository: digitaldrywood/detent
- server: codex_apps
tool: github.create_issue
repository: digitaldrywood/detent
The allowlist is a narrow exception to codex.approval_policy, not a broader
approval mode. With either never or on-request, Detent accepts an MCP
elicitation only when it correlates to exactly one pending tool call on the
same server, thread, and turn; the correlated tool is a supported workflow
deliverable mutation; and both its repository argument and the corresponding
active work-item repository match the configured tuple. Pull-request mutations
match the linked pull-request repository. Issue and Workpad mutations match the
tracked issue repository and are accepted only when it is also the deliverable
repository. Detent declines these direct Codex issue mutations when the linked
pull request belongs to another repository because MCP elicitation cannot
rewrite the already-pending arguments through the visibility-aware publication
protector. Connector-backed issue writes remain available and apply that
protector before sending content. Pull-request and issue creation and updates use
repository_full_name; issue comment creation and updates use
repo_full_name. Missing or ambiguous correlation, other mutation tools
including github.delete_issue, and repository mismatches are declined.
Command, file-change, permission, and user-input requests retain their existing
handling.
Allowlisting github.create_issue authorizes issue creation only on the
workflow's own tracked repository. This supports workers filing scoped issues
they discover while delivering the current work item; it does not authorize
issue deletion, transfer, or creation in any other repository. Those mutations
remain non-deliverable.
Allowlisting github.update_issue permits its full exposed mutation surface:
title, body, state, assignees, milestone, and labels may all be replaced. The
allowlist correlates and constrains the repository, but does not constrain the
other approved arguments.
With approval_policy: never and an empty allowlist, non-browser MCP
elicitations are declined exactly as before. chrome-devtools empty-form tool
approval remains a separate built-in behavior and is unchanged by this list.
Generated field reference
The generated block reflects the YAML-tagged Go structs reachable from the project config. It reads effective defaults through the real normalization path and derives validation text by exercising boundary values through the real loader and validators. “None” means no field-level rule was surfaced; subsystem interactions can still constrain a value.
Run go generate ./... after changing the config structs, defaults, or
validators. make check compares the committed generated artifacts with fresh
rendering and fails on drift.
budget.per_issue_max_usd and agent.budget.per_issue_max_usd are
estimate-based admission backstops, not mid-session spend ceilings. Detent
checks cumulative issue spend plus a preflight session estimate before starting
the agent. Actual session cost can exceed that estimate and the configured
backstop. Once the next projected dispatch would exceed the backstop, Detent
parks the issue on a hard hold until an operator raises or disables the cap, or
explicitly retries after resetting the hold. refusal_cooldown_seconds applies
only to resettable budget pacing and never clears a per-issue hard hold.
| Key | Type | Default | Required | Validation |
|---|---|---|---|---|
active_hours |
object |
see child fields |
Conditional | .timezone: is required when active_hours is set.timezone: must be a valid IANA timezone.windows: must contain at least one recurring window |
active_hours.timezone |
string |
none |
No | None |
active_hours.windows |
list<string> |
[] |
No | values: must use - :-: |
agent |
object |
see child fields |
No | None |
agent.auto_promote |
object |
see child fields |
No | None |
agent.auto_promote.allowed_issue_labels |
list<string> |
[] |
No | labels must not be blank |
agent.auto_promote.enabled |
boolean |
false |
No | None |
agent.auto_promote.gate_wait_state |
string |
"source" |
No | must be one of source, review |
agent.auto_promote.gate_wait_timeout_action |
string |
"human_review" |
No | must be one of merge, human_review |
agent.auto_promote.gate_wait_timeout_seconds |
integer |
3600 |
No | must be greater than 0 |
agent.auto_promote.optout_label |
string |
none |
No | None |
agent.auto_promote.pass_state |
string |
"Merging" |
No | None |
agent.auto_promote.quiet_seconds |
integer |
600 |
No | must be greater than or equal to 0 |
agent.auto_promote.rework_state |
string |
"Rework" |
No | None |
agent.auto_promote.source_state |
string |
"Human Review" |
No | None |
agent.budget |
object |
see child fields |
No | None |
agent.budget.billing_mode |
string |
none |
No | must be one of metered, subscription |
agent.budget.enabled |
boolean |
false |
No | None |
agent.budget.override_max_duration_seconds |
integer |
172800 |
No | must be greater than 0 |
agent.budget.override_max_multiplier |
number |
3 |
No | must be greater than 0 |
agent.budget.per_day_max_usd |
number |
50 |
No | must be greater than 0 |
agent.budget.per_issue_max_usd |
number |
5 |
No | must be greater than 0 |
agent.budget.pricing_path |
string |
"priv/pricing/models.yaml" |
No | is required |
agent.budget.refusal_cooldown_seconds |
integer |
3600 |
No | must be greater than or equal to 0 |
agent.checkpoint_interval_ms |
integer |
0 |
No | must be greater than or equal to 0 |
agent.dispatch_priority_by_label |
list<string> |
[] |
No | labels must not be blank |
agent.dispatch_priority_by_state |
list<string> |
[] |
No | state names must be uniquestate names must not be blank |
agent.effort |
object |
see child fields |
No | None |
agent.effort.code |
string |
none |
No | must be one of low, medium, high, xhigh, max, ultracode |
agent.effort.merge |
string |
none |
No | must be one of low, medium, high, xhigh, max, ultracode |
agent.effort.rework |
string |
none |
No | must be one of low, medium, high, xhigh, max, ultracode |
agent.experimental_thread_resume |
boolean |
true |
No | None |
agent.failure_breaker |
object |
see child fields |
No | None |
agent.failure_breaker.cooldown_seconds |
integer |
3600 |
No | must be greater than 0 |
agent.failure_breaker.same_class_limit |
integer |
5 |
No | must be greater than 0 |
agent.failure_breaker.window_seconds |
integer |
3600 |
No | must be greater than 0 |
agent.followups |
object |
see child fields |
No | None |
agent.followups.enabled |
boolean |
true |
No | None |
agent.instructions_by_state |
mapping<string, string> |
{} |
No | state "invalid" must reference a configured workflow state |
agent.instructions_by_transition |
mapping<string, mapping<string, string>> |
{} |
No | source state "invalid" must reference a configured workflow state |
agent.knowledge |
object |
see child fields |
No | None |
agent.knowledge.enabled |
boolean |
true |
No | None |
agent.knowledge.max_bytes |
integer |
65536 |
No | None |
agent.knowledge.sources |
list<object> |
[] |
No | None |
agent.knowledge.sources[].name |
string |
none |
No | None |
agent.knowledge.sources[].path |
string |
none |
Conditional | must not be blank |
agent.lessons |
object |
see child fields |
No | None |
agent.lessons.enabled |
boolean |
false |
No | None |
agent.lessons.max_entries |
integer |
50 |
No | must be greater than 0 |
agent.lessons.path |
string |
".detent/lessons.md" |
No | must be a relative path inside the workspace |
agent.lessons.postmortem_max_tokens |
integer |
1024 |
No | must be greater than 0 |
agent.lessons.recall_n |
integer |
10 |
No | must be greater than or equal to 0 |
agent.lifetime_limit_override_label |
string |
"allow-lifetime-limit" |
No | None |
agent.lifetime_session_limit |
integer |
120 |
No | must be greater than or equal to 0 |
agent.lifetime_token_limit |
integer |
750000000 |
No | must be greater than or equal to 0 |
agent.max_concurrent_agents |
integer |
10 |
No | must be greater than 0 |
agent.max_concurrent_agents_by_state |
mapping<string, integer> |
{} |
No | limits must be positive integers |
agent.max_retry_backoff_ms |
integer |
300000 |
No | must be greater than 0 |
agent.max_session_context_multiplier |
number |
0 |
No | must be greater than or equal to 0 |
agent.max_session_duration_ms |
integer |
7200000 |
No | must be greater than or equal to 0 |
agent.max_session_token_override_field |
string |
none |
No | None |
agent.max_session_token_override_label |
string |
none |
No | None |
agent.max_session_tokens |
integer |
0 |
No | must be greater than or equal to 0 |
agent.max_turn_duration_ms |
integer |
0 |
No | must be greater than or equal to 0 |
agent.max_turns |
integer |
20 |
No | must be greater than 0 |
agent.merge_fast_path |
object |
see child fields |
No | None |
agent.merge_fast_path.enabled |
boolean |
true |
No | None |
agent.merge_fast_path.fairness_age_seconds |
integer |
7200 |
No | must be greater than 0 |
agent.merge_worker_max_duration_ms |
integer |
21600000 |
No | must be greater than 0 |
agent.merge_worker_startup_timeout_ms |
integer |
240000 |
No | must be greater than 0 |
agent.no_progress_spend_limit_usd |
number |
3 |
No | must be greater than or equal to 0 |
agent.no_progress_timeout_ms |
integer |
5400000 |
No | must be greater than or equal to 0 |
agent.no_progress_token_limit |
integer |
25000000 |
No | must be greater than or equal to 0 |
agent.output_truncation |
object |
see child fields |
No | None |
agent.output_truncation.max_bytes |
integer |
0 |
No | must be greater than or equal to 0 |
agent.overload_retry_delay_ms |
integer |
45000 |
No | must be greater than 0 |
agent.prioritize_unblockers |
boolean |
true |
No | None |
agent.rate_window_pacing |
object |
see child fields |
No | None |
agent.rate_window_pacing.floor_percent |
number |
20 |
No | must be greater than 0 and less than or equal to 100 |
agent.rate_window_pacing.mode |
string |
"proportional" |
No | must be one of proportional, off, floor |
agent.rate_window_pacing.stale_after_seconds |
integer |
900 |
No | must be greater than 0 |
agent.resume_orphaned_sessions |
boolean |
true |
No | None |
agent.shutdown |
object |
see child fields |
No | None |
agent.shutdown.drain_timeout_ms |
integer |
75000 |
No | must be greater than or equal to 0 |
agent.skills |
object |
see child fields |
No | None |
agent.skills.creation |
object |
see child fields |
No | None |
agent.skills.creation.enabled |
boolean |
true |
No | None |
agent.skills.creation.max_drafts_per_run |
integer |
1 |
No | must be greater than 0 |
agent.skills.enabled |
boolean |
true |
No | None |
agent.skills.max_skills_in_prompt |
integer |
100 |
No | must be greater than 0 |
agent.skills.path |
string |
".detent/skills" |
No | must be a relative path inside the workspace |
agent.stop_run |
object |
see child fields |
No | None |
agent.stop_run.target_state |
string |
"Blocked" |
No | must be included in tracker.lanes with role holding |
agents |
object |
see child fields |
No | None |
agents.backends |
list<object> |
[] |
No | None |
agents.backends[].command |
string |
none |
Conditional | is required |
agents.backends[].disabled |
boolean |
false when configured |
No | None |
agents.backends[].id |
string |
none |
Conditional | is required |
agents.backends[].kind |
string |
none |
Conditional | is required |
agents.backends[].options |
mapping |
see child fields |
No | None |
agents.backends[].options.allowed_tools |
list<string> |
[] for Claude Code |
No | None |
agents.backends[].options.approval_policy |
string or mapping |
{"reject":{"mcp_elicitations":true,"rules":true,"sandbox_approval":true}} for Codex |
No | None |
agents.backends[].options.deliverable_elicitation_allowlist |
list<object> |
[] for Codex |
Conditional | values.repository must be owner/namevalues.server is requiredvalues.tool is required |
agents.backends[].options.deliverable_elicitation_allowlist[].repository |
string |
none |
No | None |
agents.backends[].options.deliverable_elicitation_allowlist[].server |
string |
none |
No | None |
agents.backends[].options.deliverable_elicitation_allowlist[].tool |
string |
none |
No | None |
agents.backends[].options.disallowed_tools |
list<string> |
[] for Claude Code |
No | None |
agents.backends[].options.effort |
string |
none for Claude Code |
No | must be one of low, medium, high, xhigh, max, ultracode |
agents.backends[].options.extra_args |
list<string> |
[] for Claude Code |
No | None |
agents.backends[].options.include_partial_messages |
boolean |
false for Claude Code |
No | None |
agents.backends[].options.model_provider |
string |
none for Codex |
No | must be a sanitized label containing only letters, numbers, dots, underscores, or hyphens |
agents.backends[].options.permission_mode |
string |
"bypassPermissions" for Claude Code |
No | must be one of default, acceptEdits, bypassPermissionsmust not be plan for unattended workers |
agents.backends[].options.read_timeout_ms |
integer |
5000 for Codex |
No | must be greater than or equal to 0 |
agents.backends[].options.service_tier |
string |
none for Codex |
No | None |
agents.backends[].options.session_dir |
string |
none for Pi |
No | None |
agents.backends[].options.shell |
string |
platform default shell |
No | None |
agents.backends[].options.stall_timeout_ms |
integer |
Codex: 300000; Claude Code: 0; Pi: 0 |
No | must be greater than or equal to 0 |
agents.backends[].options.thinking_level |
string |
none for Pi |
No | must be one of off, minimal, low, medium, high, xhigh, max |
agents.backends[].options.thread_sandbox |
string |
"workspace-write" for Codex |
No | None |
agents.backends[].options.tools |
list<string> |
["read","bash","edit","write","grep","find","ls"] for Pi |
No | must contain only Pi built-in tools: read, bash, edit, write, grep, find, ls |
agents.backends[].options.turn_sandbox_policy |
mapping<string, value> |
{} for Codex |
No | None |
agents.backends[].options.turn_timeout_ms |
integer |
Codex: 3600000; Claude Code: 0; Pi: 0 |
No | must be greater than or equal to 0 |
agents.backends[].protocol |
string |
none |
No | must be app-server for codexmust be headless for claude_codemust be rpc for pi_agent |
agents.backends[].provider |
string |
none |
No | must be a sanitized label containing only letters, numbers, dots, underscores, or hyphens |
agents.routes |
list<object> |
[] |
No | None |
agents.routes[].backend |
string |
none |
Conditional | is requiredmust reference a configured backend |
agents.routes[].default |
boolean |
false when configured |
No | None |
agents.routes[].disabled |
boolean |
false when configured |
No | None |
agents.routes[].model |
string |
none |
No | None |
agents.routes[].model_field |
string |
none |
No | None |
agents.routes[].name |
string |
none |
No | None |
agents.routes[].role |
string |
none |
No | None |
agents.routes[].selector |
object |
see child fields |
No | None |
agents.routes[].selector.and |
list<mapping> |
[] |
No | None |
agents.routes[].selector.assignee_in |
list<string> |
[] |
No | None |
agents.routes[].selector.author_in |
list<string> |
[] |
No | None |
agents.routes[].selector.fields |
list<object> |
[] |
No | None |
agents.routes[].selector.fields[].name |
string |
none |
No | None |
agents.routes[].selector.fields[].value |
string |
none |
No | None |
agents.routes[].selector.labels |
object |
see child fields |
No | None |
agents.routes[].selector.labels.exclude |
list<string> |
[] |
No | None |
agents.routes[].selector.labels.include |
list<string> |
[] |
No | None |
agents.routes[].selector.or |
list<mapping> |
[] |
No | None |
agents.routes[].selector.priority_in |
list<integer> |
[] |
No | values must be integers 1 through 4 |
backlog_admission |
object |
see child fields |
No | None |
backlog_admission.authors |
object |
see child fields |
No | None |
backlog_admission.authors.allow |
list<string> |
[] |
No | None |
backlog_admission.authors.allow_association |
list<string> |
[] |
No | requires author association, but tracker.kind hub_native cannot supply itvalues must be one of OWNER, MEMBER, COLLABORATOR, CONTRIBUTOR, FIRST_TIME_CONTRIBUTOR, NONE |
backlog_admission.auto_admit |
boolean |
false |
No | None |
backlog_admission.auto_admit_by_label |
mapping<string, boolean> |
{} |
No | None |
backlog_admission.auto_admit_min_confidence |
number |
0.9 |
No | None |
backlog_admission.criteria_section |
string |
none |
Conditional | is required |
backlog_admission.effort_file |
string |
"WORKFLOW.md" |
No | must be WORKFLOW.md or AGENTS.md |
backlog_admission.effort_section |
string |
none |
Conditional | is required when require_effort is true |
backlog_admission.enabled |
boolean |
false |
No | None |
backlog_admission.exclude_labels |
list<string> |
[] |
No | None |
backlog_admission.max_candidates_per_run |
integer |
50 |
No | must be greater than 0 |
backlog_admission.max_open_proposals |
integer |
10 |
No | must be greater than 0 |
backlog_admission.max_proposals_per_run |
integer |
3 |
No | must be greater than 0 |
backlog_admission.proposal_expiry_days |
integer |
7 |
No | must be greater than 0 |
backlog_admission.require_effort |
boolean |
false |
No | None |
backlog_admission.schedule |
string |
"*/15 * * * *" |
No | must be a valid five-field cron expression |
backlog_admission.sources |
object |
see child fields |
No | must configure at least one selector |
backlog_admission.sources.labels |
list<string> |
[] |
No | requires candidate selector labels, but tracker.kind hub_native does not declare complete label reads |
backlog_admission.sources.states |
list<string> |
[] |
No | requires candidate selector states, but tracker.kind hub_native does not declare itvalues must differ from target_statevalues must name a configured workflow state |
backlog_admission.sources.untracked |
boolean |
false |
No | requires candidate selector untracked, but tracker.kind hub_native does not provide github label status drift |
backlog_admission.target_state |
string |
none |
Conditional | is requiredmust name a configured workflow state |
budget |
object |
see child fields |
No | None |
budget.billing_mode |
string |
none |
No | must be one of metered, subscription |
budget.enabled |
boolean |
false |
No | None |
budget.override_max_duration_seconds |
integer |
172800 |
No | must be greater than 0 |
budget.override_max_multiplier |
number |
3 |
No | must be greater than 0 |
budget.per_day_max_usd |
number |
50 |
No | must be greater than 0 |
budget.per_issue_max_usd |
number |
5 |
No | must be greater than 0 |
budget.pricing_path |
string |
"priv/pricing/models.yaml" |
No | is required |
budget.refusal_cooldown_seconds |
integer |
3600 |
No | must be greater than or equal to 0 |
codex |
object |
see child fields |
No | agents.backends.protocol must be app-server for codex |
codex.approval_policy |
string or mapping |
{"reject":{"mcp_elicitations":true,"rules":true,"sandbox_approval":true}} |
No | None |
codex.command |
string |
"codex app-server" |
No | is required |
codex.deliverable_elicitation_allowlist |
list<object> |
[] |
No | None |
codex.deliverable_elicitation_allowlist[].repository |
string |
none |
No | must be owner/name |
codex.deliverable_elicitation_allowlist[].server |
string |
none |
Conditional | is required |
codex.deliverable_elicitation_allowlist[].tool |
string |
none |
Conditional | is required |
codex.model_provider |
string |
none |
No | must be a sanitized label containing only letters, numbers, dots, underscores, or hyphens |
codex.read_timeout_ms |
integer |
5000 |
No | must be greater than 0 |
codex.service_tier |
string |
none |
No | None |
codex.shell |
string |
platform default shell |
No | None |
codex.stall_timeout_ms |
integer |
300000 |
No | must be greater than or equal to 0 |
codex.thread_sandbox |
string |
"workspace-write" |
No | None |
codex.turn_sandbox_policy |
mapping<string, value> |
{"type":"workspaceWrite"} |
No | None |
codex.turn_timeout_ms |
integer |
3600000 |
No | must be greater than 0 |
deliverable |
object |
see child fields |
No | warning: release.enabled: release automation is unavailable for tracker.kind hub_native, including deliverable.github_pull_request: true; requires tracker.kind github |
deliverable.github_pull_request |
boolean |
false |
No | None |
deliverable.kind |
string |
"pull_request" |
No | must be one of pull_request, artifact |
deliverable.merge_method |
string |
"squash" |
No | must be one of squash, merge, rebase |
deliverable.output_root |
string |
none |
No | None |
deliverable.review_url |
string |
none |
No | None |
dependencies |
object |
see child fields |
No | None |
dependencies.source |
string |
"merged" |
No | must be one of merged, native_only |
gate |
object |
see child fields |
No | None |
gate.approval_label |
string |
"human-approved" |
No | None |
gate.artifact |
object |
see child fields |
No | None |
gate.artifact.pass_statuses |
list<string> |
["approved","complete","completed","pass","passed","valid"] |
No | None |
gate.artifact.rework_statuses |
list<string> |
["changes_requested","failed","invalid","rework"] |
No | None |
gate.artifact.status_field |
string |
"validation_status" |
No | None |
gate.artifact.wait_statuses |
list<string> |
["pending","review","reviewing","waiting"] |
No | None |
gate.automated_review |
string |
"required" |
No | must be one of required, optional, off |
gate.ci_failure_action |
string |
"rework" |
No | must be one of skip, rework |
gate.ci_trigger_label |
string |
none |
No | None |
gate.ci_trigger_label_stagger_seconds |
integer |
none |
No | must be greater than 0 |
gate.kind |
string |
"command" |
No | must be one of command, human_review, artifact |
gate.landing_mode |
string |
"per_landing" |
No | must be per_landing |
gate.local_status |
string |
none |
No | None |
gate.require_automated_review |
boolean |
true |
No | None |
gate.required_status_checks |
list<string> |
[] |
No | None |
gate.run |
string |
"make check" |
No | None |
gate.security_audit |
object |
see child fields |
No | None |
gate.security_audit.block_on |
list<string> |
["p1","p2"] |
No | severities must be p1, p2, or p3 |
gate.security_audit.enabled |
boolean |
true |
No | None |
gate.security_audit.max_attempts |
integer |
3 |
No | must be greater than 0 |
gate.security_audit.max_diff_bytes |
integer |
262144 |
No | must be greater than 0 |
gate.security_audit.model |
string |
none |
No | None |
gate.security_audit.turn_timeout_ms |
integer |
1200000 |
No | must be greater than 0 |
gate.transient_ci_retry_limit |
integer |
2 |
No | must be greater than or equal to 0 |
gate.validator |
object |
see child fields |
No | None |
gate.validator.block_on |
list<string> |
["p1"] |
No | None |
gate.validator.enabled |
boolean |
false |
No | None |
gate.validator.max_attempts |
integer |
3 |
No | must be greater than 0 |
gate.validator.max_inline_diff_bytes |
integer |
65536 |
No | must be greater than or equal to 0 |
gate.validator.min_score |
number |
0.8 |
No | must be greater than 0 and less than or equal to 1 |
gate.validator.model |
string |
none |
No | None |
gate.validator.turn_timeout_ms |
integer |
0 |
No | must be greater than or equal to 0 |
gate.validator.unverified_criteria |
string |
"rework" |
No | must be rework or pass-with-disclosure |
hooks |
object |
see child fields |
No | None |
hooks.after_create |
string |
none |
No | None |
hooks.after_run |
string |
none |
No | None |
hooks.before_remove |
string |
none |
No | None |
hooks.before_run |
string |
none |
No | None |
hooks.runner_setup |
string |
none |
No | must be a repository-relative file path |
hooks.shell |
string |
platform default shell |
No | None |
hooks.timeout_ms |
integer |
60000 |
No | must be greater than 0 |
identity |
object |
see child fields |
No | None |
identity.assignee_required |
boolean |
false |
No | None |
identity.github_login |
string |
none |
No | None |
identity.name |
string |
none |
Conditional | must not be blank |
identity.owner_field |
string |
none |
No | must be blank when identity.ownership_mode is assignee |
identity.ownership_mode |
string |
none |
No | identity.owner_field must be blank when identity.ownership_mode is assigneemust be one of assignee, field |
intake |
object |
see child fields |
Conditional | tracker.repository is required for intake sources |
intake.sources |
list<object> |
[] |
No | requires tracker.kind githubvalues path filters require a scheduled source |
intake.sources[].creates |
object |
see child fields |
No | None |
intake.sources[].creates.body |
string |
"{details}" when configured |
No | None |
intake.sources[].creates.labels |
list<string> |
[] |
No | None |
intake.sources[].creates.status |
string |
"Backlog" when configured |
No | must name a configured tracker state |
intake.sources[].creates.title |
string |
"[{source}] {summary}" when configured |
No | None |
intake.sources[].cron |
string |
none |
No | None |
intake.sources[].dedupe_by |
string |
"fingerprint" when configured |
No | None |
intake.sources[].enabled |
boolean |
true when configured |
No | None |
intake.sources[].exclude_paths |
list<string> |
[] |
No | None |
intake.sources[].kind |
string |
none |
Conditional | is required |
intake.sources[].match |
string |
none |
No | must use field:value syntax |
intake.sources[].name |
string |
none |
Conditional | is requiredmust contain only lowercase letters, numbers, dots, underscores, or hyphens |
intake.sources[].paths |
list<string> |
[] |
No | None |
intake.sources[].scan |
string |
none |
No | None |
intake.sources[].secret |
string |
none |
Conditional | is required for webhook sources |
observability |
object |
see child fields |
No | None |
observability.dashboard_enabled |
boolean |
true |
No | None |
observability.dispatch_stall_threshold_seconds |
integer |
7200 |
No | must be greater than 0 |
observability.efficiency |
object |
see child fields |
No | None |
observability.efficiency.anomaly_dwell_multiple |
number |
3 |
No | must be greater than 0 |
observability.efficiency.anomaly_sessions_multiple |
number |
3 |
No | must be greater than 0 |
observability.efficiency.anomaly_tokens_multiple |
number |
3 |
No | must be greater than 0 |
observability.otlp |
object |
see child fields |
No | None |
observability.otlp.endpoint |
string |
none |
No | must be an absolute http or https URL |
observability.otlp.headers |
mapping<string, string> |
{} |
No | None |
observability.otlp.service_name |
string |
"detent" |
No | None |
observability.otlp.timeout_ms |
integer |
5000 |
No | None |
observability.park_review_threshold |
integer |
3 |
No | must be greater than 0 |
observability.refresh_ms |
integer |
1000 |
No | must be greater than 0 |
observability.render_interval_ms |
integer |
16 |
No | must be greater than 0 |
observability.staleness |
object |
see child fields |
No | None |
observability.staleness.enabled |
boolean |
true |
No | None |
observability.staleness.human_gate_rearm_hours |
integer |
168 |
No | must be greater than 0 |
observability.staleness.lane_reentry_window_hours |
integer |
168 |
No | must be greater than 0 |
observability.staleness.lanes |
list<object> |
[{"State":"Human Review","ThresholdHours":72,"HumanGate":true},{"State":"Blocked","ThresholdHours":48,"HumanGate":false},{"State":"Merging","ThresholdHours":2,"HumanGate":false}] |
No | None |
observability.staleness.lanes[].human_gate |
boolean |
true |
No | None |
observability.staleness.lanes[].state |
string |
"Human Review" |
Conditional | is required |
observability.staleness.lanes[].threshold_hours |
integer |
72 |
No | must be greater than 0 |
observability.staleness.no_completion_hours |
integer |
24 |
No | must be greater than 0 |
observability.staleness.no_merge_hours |
integer |
12 |
No | must be greater than 0 |
observability.staleness.repeated_decision_benign_reasons |
list<string> |
["already_running","blocked_by_dependency","github_rest_capacity_paused","github_rest_recovery","global_capacity_full","outside_active_window","project_capacity_full","provider_rate_window_backpressure","ready_merge_control_limit"] |
No | contains "invalid", which does not match a scheduler-emitted reason; emitted reasons include ["already_running" "authorization_selector_declined" "blocked_by_dependency" "github_rest_capacity_paused" "github_rest_recovery" "global_capacity_full" "outside_active_window" "provider_rate_window_backpressure"]contains "todo", which does not match a scheduler-emitted reason; emitted reasons include ["already_running" "authorization_selector_declined" "blocked_by_dependency" "github_rest_capacity_paused" "github_rest_recovery" "global_capacity_full" "outside_active_window" "provider_rate_window_backpressure"] |
observability.staleness.repeated_decision_count |
integer |
20 |
No | must be greater than 0 |
observability.staleness.repeated_window_hours |
integer |
24 |
No | must be greater than 0 |
observability.staleness.webhook |
object |
see child fields |
No | None |
observability.staleness.webhook.headers |
mapping<string, string> |
{} |
No | None |
observability.staleness.webhook.timeout_ms |
integer |
5000 |
No | None |
observability.staleness.webhook.url |
string |
none |
No | must be an absolute http or https URL |
observability.stranded_active_threshold_seconds |
integer |
600 |
No | must be greater than 0 |
operator |
object |
see child fields |
No | None |
operator.actions |
list<string> |
["return_retired_parks","clear_closed_dependencies","restore_stuck_merging"] |
No | must contain only return_retired_parks, clear_closed_dependencies, restore_stuck_merging, merge_when_wedged |
operator.merge_wedge_seconds |
integer |
7200 |
No | must be greater than 0 |
plan |
object |
see child fields |
No | agents.backends.options.permission_mode must not be plan for unattended workers |
plan.approval_label |
string |
"plan-approved" |
No | None |
plan.enabled |
boolean |
false |
No | None |
plan.review |
string |
"human" |
No | must be one of human, automated, both |
plan.stop |
string |
"Plan Review" |
No | None |
polling |
object |
see child fields |
No | None |
polling.conditional |
boolean |
true |
No | None |
polling.interval_ms |
integer |
120000 |
No | must be at least 60000must be greater than 0 |
polling.refresh_failure_threshold |
integer |
3 |
No | must be greater than 0 |
recovery |
object |
see child fields |
No | None |
recovery.terminal_attempt_retry_limit |
integer |
3 |
No | must be greater than or equal to 0 |
release |
object |
see child fields |
No | warning: release.enabled: release automation is unavailable for tracker.kind hub_native, including deliverable.github_pull_request: true; requires tracker.kind github |
release.enabled |
boolean |
false |
No | release.max_age_hours must be greater than 0 when release.enabled is truerelease.min_merged_issues must be greater than 0 when release.enabled is truerelease.require_green_ci must be true when release.enabled is truetracker.repository must be owner/name when release.enabled is true |
release.flaky_check_names |
list<string> |
[] |
No | must not be empty when release.rerun_flaky_once is true |
release.max_age_hours |
integer |
24 |
No | must be greater than 0 when release.enabled is true |
release.min_merged_issues |
integer |
5 |
No | must be greater than 0 when release.enabled is true |
release.require_green_ci |
boolean |
true |
No | must be true when release.enabled is true |
release.required_check_names |
list<string> |
[] |
No | None |
release.rerun_flaky_once |
boolean |
false |
No | release.flaky_check_names must not be empty when release.rerun_flaky_once is true |
release.version_bump |
string |
"auto" |
No | must be auto |
retro |
object |
see child fields |
No | None |
retro.allow_public_cross_project_details |
boolean |
false |
No | None |
retro.daily_issue_cap |
integer |
3 when configured |
No | must be greater than 0 |
retro.enabled |
boolean |
false |
No | requires tracker.kind github |
retro.fallback_threshold |
integer |
3 when configured |
No | must be at least 2 |
retro.labels |
list<string> |
["retro"] when configured |
No | None |
retro.lookback_days |
integer |
7 when configured |
No | must be greater than 0 |
retro.min_occurrences |
integer |
2 when configured |
No | must be at least 2 |
retro.product_repository |
string |
"digitaldrywood/detent" when configured |
No | must be owner/name |
retro.receipt_baseline_multiple |
number |
4 when configured |
No | must be greater than 1 |
retro.schedule |
string |
"0 3 * * *" when configured |
No | must be a valid five-field cron expression |
retro.single_occurrence_severity |
string |
"critical" when configured |
No | must be one of info, warning, high, critical |
retro.target_state |
string |
"Backlog" when configured |
No | must name a configured tracker state |
review |
object |
see child fields |
No | agent.auto_promote.gate_wait_state must be one of source, review |
review.human |
boolean |
false |
No | None |
routines |
list<object> |
[] |
No | requires tracker.kind github |
routines[].labels |
list<string> |
[] |
Conditional | labels must not be blank |
routines[].max_findings_per_run |
integer |
3 when configured |
No | must be greater than 0 |
routines[].max_open_findings |
integer |
10 when configured |
No | must be greater than 0 |
routines[].name |
string |
none |
No | must be a sanitized label containing only letters, numbers, dots, underscores, or hyphens |
routines[].prompt |
string |
none |
Conditional | is required |
routines[].schedule |
string |
none |
Conditional | is requiredmust be a valid five-field cron expression |
routines[].target_state |
string |
"Todo" when configured |
No | must name a configured workflow state |
runners |
object |
see child fields |
No | None |
runners.profile |
string |
none |
No | must name a declared runner profile |
runners.profiles |
mapping<string, mapping> |
{} |
No | names must be lowercase ASCII tokens of at most 64 characters |
runners.profiles.<name> |
object |
see child fields |
No | None |
runners.profiles.<name>.machine_id |
string |
none |
No | None |
runners.profiles.<name>.required_tags |
list<string> |
[] |
No | None |
runners.profiles.<name>.runner_id |
string |
none |
No | None |
schedule_ownership |
object |
see child fields |
No | None |
schedule_ownership.backend |
string |
"github_ref" |
No | must be github_ref |
schedule_ownership.branch |
string |
"detent-schedule-coordination" |
No | must be a valid branch name |
schedule_ownership.enabled |
boolean |
false |
No | must be true when scheduled work is configured; add a schedule_ownership block with enabled: true and a shared key |
schedule_ownership.endpoint |
string |
none |
No | None |
schedule_ownership.heartbeat_seconds |
integer |
300 |
No | must be greater than zeromust leave more than twice max_clock_skew_seconds before lease expiry |
schedule_ownership.key |
string |
none |
Conditional | is required when enabled is true |
schedule_ownership.lease_seconds |
integer |
900 |
No | must be greater than zero |
schedule_ownership.max_clock_skew_seconds |
integer |
15 |
No | must be less than half lease_secondsmust not be negative |
schedule_ownership.repository |
string |
none |
No | must use owner/name syntaxnative schedule ownership requires an explicit schedule_ownership.repository to opt into GitHub coordination |
schedule_ownership.retry_seconds |
integer |
120 |
No | must be greater than zero |
server |
object |
see child fields |
No | None |
server.board_snapshot_stale_after_seconds |
integer |
900 |
No | must be greater than 0 |
server.host |
string |
"127.0.0.1" |
No | is required |
server.kanban |
object |
see child fields |
No | None |
server.kanban.allowed_transitions |
mapping<string, list<string>> |
{} |
No | None |
server.kanban.issue_state_field_id |
integer |
0 |
No | must be greater than 0 when set |
server.kanban.mode |
string |
"read_only" |
No | must be one of read_only, integration |
server.kanban.show_blocked_alerts |
boolean |
false |
No | None |
server.port |
integer |
none |
No | must be greater than or equal to 0 |
tracker |
object |
see child fields |
No | intake.sources[].creates.status must name a configured tracker stateretro.target_state must name a configured tracker state |
tracker.api_key |
string |
none |
Conditional | or GitHub App credentials are required for github |
tracker.assignee |
string |
none |
No | None |
tracker.authorization |
object |
see child fields |
No | None |
tracker.authorization.and |
list<mapping> |
[] |
No | None |
tracker.authorization.assignee_in |
list<string> |
[] |
No | None |
tracker.authorization.author_in |
list<string> |
[] |
No | None |
tracker.authorization.fields |
list<object> |
[] |
No | None |
tracker.authorization.fields[].name |
string |
none |
Conditional | must not be blank |
tracker.authorization.fields[].value |
string |
none |
Conditional | must not be blank |
tracker.authorization.labels |
object |
see child fields |
No | None |
tracker.authorization.labels.exclude |
list<string> |
[] |
No | None |
tracker.authorization.labels.include |
list<string> |
[] |
No | None |
tracker.authorization.or |
list<mapping> |
[] |
No | None |
tracker.authorization.priority_in |
list<integer> |
[] |
No | values must be integers 1 through 4 |
tracker.auto_provision |
boolean |
true |
No | None |
tracker.blocked_recovery |
object |
see child fields |
No | None |
tracker.blocked_recovery.breaker_cooldown_seconds |
integer |
86400 |
No | must be greater than or equal to 0 |
tracker.blocked_recovery.enabled |
boolean |
false |
Conditional | tracker.blocked_recovery.reason_codes must not be empty when tracker.blocked_recovery.enabled is truetracker.blocked_recovery.source_states must not be empty when tracker.blocked_recovery.enabled is truetracker.blocked_recovery.target_state is required when tracker.blocked_recovery.enabled is truetracker.lanes with role active must include tracker.blocked_recovery.target_state when tracker.blocked_recovery.enabled is true |
tracker.blocked_recovery.reason_codes |
list<string> |
["merge_conflict","stale_base","missing_current_head_ci"] |
No | must contain only merge_conflict, stale_base, missing_current_head_cimust not be empty when tracker.blocked_recovery.enabled is true |
tracker.blocked_recovery.source_states |
list<string> |
["blocked"] |
No | must not be empty when tracker.blocked_recovery.enabled is truestate names must be uniquestate names must not be blank |
tracker.blocked_recovery.target_state |
string |
"Rework" |
Conditional | is required when tracker.blocked_recovery.enabled is truetracker.lanes with role active must include tracker.blocked_recovery.target_state when tracker.blocked_recovery.enabled is true |
tracker.blocker_auto_promote |
object |
see child fields |
No | None |
tracker.blocker_auto_promote.blocker_states |
list<string> |
["backlog","blocked","human review"] |
No | state names must be uniquestate names must not be blank |
tracker.blocker_auto_promote.enabled |
boolean |
false |
Conditional | tracker.blocker_auto_promote.target_state is required when tracker.blocker_auto_promote.enabled is true |
tracker.blocker_auto_promote.source_states |
list<string> |
[] |
No | state names must be uniquestate names must not be blank |
tracker.blocker_auto_promote.target_state |
string |
"Todo" |
Conditional | is required when tracker.blocker_auto_promote.enabled is true |
tracker.claims |
object |
see child fields |
No | None |
tracker.claims.enabled |
boolean |
false |
Conditional | tracker.claims.heartbeat_seconds must be greater than 0 when tracker.claims.enabled is truetracker.claims.lease_field must not be blank when tracker.claims.enabled is truetracker.claims.ttl_seconds must be greater than 0 when tracker.claims.enabled is true |
tracker.claims.heartbeat_seconds |
integer |
0 |
No | must be greater than 0 when tracker.claims.enabled is true |
tracker.claims.lease_field |
string |
none |
Conditional | must not be blank when tracker.claims.enabled is true |
tracker.claims.ttl_seconds |
integer |
0 |
No | must be greater than 0 when tracker.claims.enabled is true |
tracker.dependency_auto_unblock |
object |
see child fields |
No | None |
tracker.dependency_auto_unblock.enabled |
boolean |
false |
Conditional | tracker.dependency_auto_unblock.source_states must not be empty when tracker.dependency_auto_unblock.enabled is truetracker.dependency_auto_unblock.target_state is required when tracker.dependency_auto_unblock.enabled is truetracker.lanes with role active must include Rework when tracker.dependency_auto_unblock.enabled is true |
tracker.dependency_auto_unblock.readiness |
string |
"terminal_or_merged" |
No | must be one of terminal, terminal_or_merged |
tracker.dependency_auto_unblock.source_states |
list<string> |
["blocked"] |
No | must not be empty when tracker.dependency_auto_unblock.enabled is truestate names must be uniquestate names must not be blank |
tracker.dependency_auto_unblock.target_state |
string |
"Todo" |
Conditional | is required when tracker.dependency_auto_unblock.enabled is true |
tracker.endpoint |
string |
"https://api.github.com/graphql" for github; unused otherwise |
No | None |
tracker.github_app_id |
string |
none |
Conditional | is required for github app |
tracker.github_app_installation_id |
string |
none |
Conditional | is required for github app |
tracker.github_app_private_key |
string |
none |
Conditional | or tracker.github_app_private_key_path is required for github app |
tracker.github_app_private_key_path |
string |
none |
Conditional | tracker.github_app_private_key or tracker.github_app_private_key_path is required for github app |
tracker.github_graphql_min_remaining_reserve |
integer |
1000 |
No | must be greater than 0 |
tracker.github_graphql_warn_remaining |
integer |
500 |
No | must be greater than 0 |
tracker.github_rest_debug_logging |
boolean |
false |
No | None |
tracker.github_rest_fanout_max_requests |
integer |
80 |
No | must be greater than or equal to 0 |
tracker.github_rest_min_remaining_reserve |
integer |
1000 |
No | must be greater than 0 |
tracker.github_status_source |
string |
"project_v2" |
No | None |
tracker.github_unstarted_check_threshold_seconds |
integer |
900 |
No | must be greater than 0 |
tracker.github_webhook_secret |
string |
none |
No | None |
tracker.http_idle_conn_timeout_ms |
integer |
90000 |
No | must be greater than 0 |
tracker.http_max_idle_conns |
integer |
100 |
No | must be greater than 0 |
tracker.http_max_idle_conns_per_host |
integer |
32 |
No | must be greater than 0 |
tracker.issues |
list<object> |
[] |
No | None |
tracker.issues[].assigned_to_worker |
boolean |
false when configured |
No | None |
tracker.issues[].assignee_id |
string |
none |
No | None |
tracker.issues[].assignees |
list<string> |
[] |
No | None |
tracker.issues[].author_association |
string |
none |
No | None |
tracker.issues[].author_id |
string |
none |
No | None |
tracker.issues[].blocked_by |
list<object> |
[] |
No | None |
tracker.issues[].blocked_by[].human_completion_ready |
boolean |
false when configured |
No | None |
tracker.issues[].blocked_by[].human_owned |
boolean |
false when configured |
No | None |
tracker.issues[].blocked_by[].id |
string |
none |
No | None |
tracker.issues[].blocked_by[].identifier |
string |
none |
No | None |
tracker.issues[].blocked_by[].source |
string |
none |
No | None |
tracker.issues[].blocked_by[].state |
string |
none |
No | None |
tracker.issues[].blocker_reason |
string |
none |
No | None |
tracker.issues[].branch_name |
string |
none |
No | None |
tracker.issues[].child_issues |
list<object> |
[] |
No | None |
tracker.issues[].child_issues[].human_completion_ready |
boolean |
false when configured |
No | None |
tracker.issues[].child_issues[].human_owned |
boolean |
false when configured |
No | None |
tracker.issues[].child_issues[].id |
string |
none |
No | None |
tracker.issues[].child_issues[].identifier |
string |
none |
No | None |
tracker.issues[].child_issues[].source |
string |
none |
No | None |
tracker.issues[].child_issues[].state |
string |
none |
No | None |
tracker.issues[].closed |
boolean |
false when configured |
No | None |
tracker.issues[].closed_at |
mapping |
none |
No | None |
tracker.issues[].closed_reason |
string |
none |
No | None |
tracker.issues[].comments |
list<object> |
[] |
No | None |
tracker.issues[].comments[].author_authorized |
boolean |
false when configured |
No | None |
tracker.issues[].comments[].author_display_name |
string |
none |
No | None |
tracker.issues[].comments[].author_kind |
string |
none |
No | None |
tracker.issues[].comments[].author_login |
string |
none |
No | None |
tracker.issues[].comments[].backend |
string |
none |
No | None |
tracker.issues[].comments[].body |
string |
none |
No | None |
tracker.issues[].comments[].can_delete |
boolean |
false when configured |
No | None |
tracker.issues[].comments[].can_edit |
boolean |
false when configured |
No | None |
tracker.issues[].comments[].created_at |
mapping |
none |
No | None |
tracker.issues[].comments[].id |
string |
none |
No | None |
tracker.issues[].comments[].local |
boolean |
false when configured |
No | None |
tracker.issues[].comments[].target_type |
string |
none |
No | None |
tracker.issues[].comments[].updated_at |
mapping |
none |
No | None |
tracker.issues[].comments[].url |
string |
none |
No | None |
tracker.issues[].created_at |
mapping |
none |
No | None |
tracker.issues[].deliverable |
object |
none |
No | None |
tracker.issues[].deliverable.external_id |
string |
none |
No | None |
tracker.issues[].deliverable.kind |
string |
none |
No | None |
tracker.issues[].deliverable.metadata |
mapping<string, string> |
{} |
No | None |
tracker.issues[].deliverable.path |
string |
none |
No | None |
tracker.issues[].deliverable.review_url |
string |
none |
No | None |
tracker.issues[].deliverable.validation_status |
string |
none |
No | None |
tracker.issues[].description |
string |
none |
No | None |
tracker.issues[].field_updated_at |
mapping<string, mapping> |
{} |
No | None |
tracker.issues[].fields |
mapping<string, string> |
{} |
No | None |
tracker.issues[].id |
string |
none |
No | None |
tracker.issues[].identifier |
string |
none |
No | None |
tracker.issues[].issue_contract |
mapping |
none |
No | None |
tracker.issues[].labels |
list<string> |
[] |
No | None |
tracker.issues[].metadata |
mapping<string, string> |
{} |
No | None |
tracker.issues[].model_override |
string |
none |
No | None |
tracker.issues[].number |
integer |
0 when configured |
No | None |
tracker.issues[].pr_association_checked_at |
mapping |
see child fields |
No | None |
tracker.issues[].pr_association_source |
string |
none |
No | None |
tracker.issues[].pr_number |
integer |
none |
No | None |
tracker.issues[].pr_repository |
string |
none |
No | None |
tracker.issues[].priority |
integer |
none |
No | None |
tracker.issues[].priority_name |
string |
none |
No | None |
tracker.issues[].pull_request |
object |
none |
No | None |
tracker.issues[].pull_request.activity_at |
mapping |
none |
No | None |
tracker.issues[].pull_request.base_ref |
string |
none |
No | None |
tracker.issues[].pull_request.base_sha |
string |
none |
No | None |
tracker.issues[].pull_request.branch_name |
string |
none |
No | None |
tracker.issues[].pull_request.check_run_count |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.checks |
list<object> |
[] |
No | None |
tracker.issues[].pull_request.checks[].conclusion |
string |
none |
No | None |
tracker.issues[].pull_request.checks[].details_url |
string |
none |
No | None |
tracker.issues[].pull_request.checks[].duration_seconds |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.checks[].failure_detail |
string |
none |
No | None |
tracker.issues[].pull_request.checks[].id |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.checks[].name |
string |
none |
No | None |
tracker.issues[].pull_request.checks[].queue_seconds |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.checks[].status |
string |
none |
No | None |
tracker.issues[].pull_request.checks[].workflow_run_id |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.ci_duration_seconds |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.ci_queue_seconds |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.ci_status |
string |
none |
No | None |
tracker.issues[].pull_request.codex_review_api_state |
string |
none |
No | None |
tracker.issues[].pull_request.codex_review_body_severity |
string |
none |
No | None |
tracker.issues[].pull_request.codex_review_findings |
list<object> |
[] |
No | None |
tracker.issues[].pull_request.codex_review_findings[].body |
string |
none |
No | None |
tracker.issues[].pull_request.codex_review_findings[].line |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.codex_review_findings[].path |
string |
none |
No | None |
tracker.issues[].pull_request.codex_review_findings[].url |
string |
none |
No | None |
tracker.issues[].pull_request.codex_review_source |
string |
none |
No | None |
tracker.issues[].pull_request.codex_review_state |
string |
none |
No | None |
tracker.issues[].pull_request.codex_review_submitted_at |
mapping |
none |
No | None |
tracker.issues[].pull_request.diff_fingerprint |
string |
none |
No | None |
tracker.issues[].pull_request.draft |
boolean |
false when configured |
No | None |
tracker.issues[].pull_request.head_sha |
string |
none |
No | None |
tracker.issues[].pull_request.hydration_degraded_reason |
string |
none |
No | None |
tracker.issues[].pull_request.hydration_next_retry_at |
mapping |
none |
No | None |
tracker.issues[].pull_request.hydration_unavailable_reason |
string |
none |
No | None |
tracker.issues[].pull_request.labels |
list<string> |
[] |
No | None |
tracker.issues[].pull_request.latest_codex_review_commit_sha |
string |
none |
No | None |
tracker.issues[].pull_request.latest_codex_review_state |
string |
none |
No | None |
tracker.issues[].pull_request.latest_codex_review_submitted_at |
mapping |
none |
No | None |
tracker.issues[].pull_request.merge_queue_entry |
object |
none |
No | None |
tracker.issues[].pull_request.merge_queue_entry.base_sha |
string |
none |
No | None |
tracker.issues[].pull_request.merge_queue_entry.batching |
object |
see child fields |
No | None |
tracker.issues[].pull_request.merge_queue_entry.batching.max_group_size |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.merge_queue_entry.batching.min_group_size |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.merge_queue_entry.batching.min_group_wait_seconds |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.merge_queue_entry.depth |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.merge_queue_entry.enqueued_at |
mapping |
none |
No | None |
tracker.issues[].pull_request.merge_queue_entry.estimated_time_to_merge_seconds |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.merge_queue_entry.head_sha |
string |
none |
No | None |
tracker.issues[].pull_request.merge_queue_entry.id |
string |
none |
No | None |
tracker.issues[].pull_request.merge_queue_entry.position |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.merge_queue_entry.state |
string |
none |
No | None |
tracker.issues[].pull_request.merge_queue_entry.url |
string |
none |
No | None |
tracker.issues[].pull_request.mergeable_state |
string |
none |
No | None |
tracker.issues[].pull_request.merged_at |
mapping |
none |
No | None |
tracker.issues[].pull_request.node_id |
string |
none |
No | None |
tracker.issues[].pull_request.number |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.required_check_failures |
list<object> |
[] |
No | None |
tracker.issues[].pull_request.required_check_failures[].conclusion |
string |
none |
No | None |
tracker.issues[].pull_request.required_check_failures[].details_url |
string |
none |
No | None |
tracker.issues[].pull_request.required_check_failures[].duration_seconds |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.required_check_failures[].failure_detail |
string |
none |
No | None |
tracker.issues[].pull_request.required_check_failures[].id |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.required_check_failures[].name |
string |
none |
No | None |
tracker.issues[].pull_request.required_check_failures[].queue_seconds |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.required_check_failures[].status |
string |
none |
No | None |
tracker.issues[].pull_request.required_check_failures[].workflow_run_id |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.running_checks |
list<string> |
[] |
No | None |
tracker.issues[].pull_request.slow_checks |
list<object> |
[] |
No | None |
tracker.issues[].pull_request.slow_checks[].conclusion |
string |
none |
No | None |
tracker.issues[].pull_request.slow_checks[].details_url |
string |
none |
No | None |
tracker.issues[].pull_request.slow_checks[].duration_seconds |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.slow_checks[].failure_detail |
string |
none |
No | None |
tracker.issues[].pull_request.slow_checks[].id |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.slow_checks[].name |
string |
none |
No | None |
tracker.issues[].pull_request.slow_checks[].queue_seconds |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.slow_checks[].status |
string |
none |
No | None |
tracker.issues[].pull_request.slow_checks[].workflow_run_id |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.stale_successful_checks |
list<object> |
[] |
No | None |
tracker.issues[].pull_request.stale_successful_checks[].conclusion |
string |
none |
No | None |
tracker.issues[].pull_request.stale_successful_checks[].details_url |
string |
none |
No | None |
tracker.issues[].pull_request.stale_successful_checks[].duration_seconds |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.stale_successful_checks[].failure_detail |
string |
none |
No | None |
tracker.issues[].pull_request.stale_successful_checks[].id |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.stale_successful_checks[].name |
string |
none |
No | None |
tracker.issues[].pull_request.stale_successful_checks[].queue_seconds |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.stale_successful_checks[].status |
string |
none |
No | None |
tracker.issues[].pull_request.stale_successful_checks[].workflow_run_id |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.state |
string |
none |
No | None |
tracker.issues[].pull_request.status_context_count |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.transient_failed_checks |
list<object> |
[] |
No | None |
tracker.issues[].pull_request.transient_failed_checks[].conclusion |
string |
none |
No | None |
tracker.issues[].pull_request.transient_failed_checks[].details_url |
string |
none |
No | None |
tracker.issues[].pull_request.transient_failed_checks[].duration_seconds |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.transient_failed_checks[].failure_detail |
string |
none |
No | None |
tracker.issues[].pull_request.transient_failed_checks[].id |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.transient_failed_checks[].name |
string |
none |
No | None |
tracker.issues[].pull_request.transient_failed_checks[].queue_seconds |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.transient_failed_checks[].status |
string |
none |
No | None |
tracker.issues[].pull_request.transient_failed_checks[].workflow_run_id |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.unresolved_review_threads |
list<object> |
[] |
No | None |
tracker.issues[].pull_request.unresolved_review_threads[].body |
string |
none |
No | None |
tracker.issues[].pull_request.unresolved_review_threads[].line |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.unresolved_review_threads[].path |
string |
none |
No | None |
tracker.issues[].pull_request.unstarted_check_count |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.unstarted_checks |
list<object> |
[] |
No | None |
tracker.issues[].pull_request.unstarted_checks[].conclusion |
string |
none |
No | None |
tracker.issues[].pull_request.unstarted_checks[].details_url |
string |
none |
No | None |
tracker.issues[].pull_request.unstarted_checks[].duration_seconds |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.unstarted_checks[].failure_detail |
string |
none |
No | None |
tracker.issues[].pull_request.unstarted_checks[].id |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.unstarted_checks[].name |
string |
none |
No | None |
tracker.issues[].pull_request.unstarted_checks[].queue_seconds |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.unstarted_checks[].status |
string |
none |
No | None |
tracker.issues[].pull_request.unstarted_checks[].workflow_run_id |
integer |
0 when configured |
No | None |
tracker.issues[].pull_request.url |
string |
none |
No | None |
tracker.issues[].stage_updated_actor |
object |
see child fields |
No | None |
tracker.issues[].stage_updated_actor.kind |
string |
none |
No | None |
tracker.issues[].stage_updated_actor.login |
string |
none |
No | None |
tracker.issues[].stage_updated_at |
mapping |
none |
No | None |
tracker.issues[].state |
string |
none |
No | None |
tracker.issues[].title |
string |
none |
No | None |
tracker.issues[].unblocker_count |
integer |
0 when configured |
No | None |
tracker.issues[].updated_at |
mapping |
none |
No | None |
tracker.issues[].url |
string |
none |
No | None |
tracker.issues[].workpad_signal |
mapping |
none |
No | None |
tracker.kind |
string |
none |
Yes | backlog_admission.authors.allow_association requires author association, but tracker.kind hub_native cannot supply itbacklog_admission.sources.labels requires candidate selector labels, but tracker.kind hub_native does not declare complete label readsbacklog_admission.sources.states requires candidate selector states, but tracker.kind hub_native does not declare itbacklog_admission.sources.untracked requires candidate selector untracked, but tracker.kind hub_native does not provide github label status driftintake.sources requires tracker.kind githubis requiredmust be one of github, hub_native; migrate retired trackers to hub_nativeretro.enabled requires tracker.kind githubroutines requires tracker.kind githubwarning: release.enabled: release automation is unavailable for tracker.kind hub_native, including deliverable.github_pull_request: true; requires tracker.kind github |
tracker.lanes |
list<object> |
[{"name":"Backlog","role":"holding"},{"name":"Human Review","role":"holding"},{"name":"Blocked","role":"holding"},{"name":"Todo","role":"active"},{"name":"In Progress","role":"active"},{"name":"Closed","role":"terminal"},{"name":"Cancelled","role":"terminal"},{"name":"Canceled","role":"terminal"},{"name":"Duplicate","role":"terminal"},{"name":"Done","role":"terminal"}] |
No | agent.stop_run.target_state must be included in tracker.lanes with role holdingwith role active must include Rework when tracker.dependency_auto_unblock.enabled is truewith role active must include tracker.blocked_recovery.target_state when tracker.blocked_recovery.enabled is truewith role holding state names must not be blank |
tracker.lanes[].name |
string |
none |
Conditional | must not be blank |
tracker.lanes[].role |
string |
none |
No | must be active, holding or terminal |
tracker.local_sqlite |
object |
see child fields |
No | None |
tracker.local_sqlite.path |
string |
none |
No | None |
tracker.local_sqlite.project_id |
string |
none |
No | None |
tracker.priority_map |
string or mapping |
{"High":2,"Low":4,"Medium":3,"No priority":null,"Urgent":1} |
No | option names must not be blankranks must be integers 1 through 4 or null |
tracker.project_slug |
string |
none |
Conditional | is required for github project_v2 |
tracker.repository |
string |
none |
Conditional | is required for intake sourcesmust be owner/name when release.enabled is true |
tracker.state_map |
string or mapping |
{} |
No | state names must not be blank |
tracker.status_field |
string |
"Status" |
No | None |
tracker.status_label_prefix |
string |
"detent:" |
No | None |
tracker.status_page_url |
string |
"https://www.githubstatus.com" for github; unused otherwise |
No | must be an absolute http or https base URL without credentials, path, query, or fragment |
tracker.write_probe_issue |
string |
none |
No | None |
worker |
object |
see child fields |
No | .extra_network_domains: extra network domains must be unique lowercase exact DNS hosts |
worker.allow_local_binding |
boolean |
global.worker.allow_local_binding, otherwise false |
No | None |
worker.compute_rates |
mapping<string, mapping> |
{} |
No | None |
worker.extra_network_domains |
list<string> |
[] |
No | None |
worker.github_rest_min_remaining_reserve |
integer |
1250 |
No | must be greater than 0 |
worker.github_rest_poll_interval_ms |
integer |
60000 |
No | must be greater than or equal to 60000 |
worker.github_token |
string |
top-level github_token |
No | None |
worker.github_token_resolution_timeout_ms |
integer |
15000 |
No | must be greater than 0 |
worker.host_caps |
mapping<string, integer> |
{} |
No | .invalid must be greater than 0.invalid must name a configured worker.ssh_hosts entry |
worker.host_selection |
string |
"least_loaded" |
No | must be least_loaded or preference |
worker.max_concurrent_agents_per_host |
integer |
none |
No | must be greater than 0 |
worker.ssh_hosts |
list<string> |
[] |
No | must contain unique SSH destinations or localworker.host_caps.invalid must name a configured worker.ssh_hosts entry |
workpad |
object |
see child fields |
No | None |
workpad.structured_only |
boolean |
false |
No | None |
workspace |
object |
see child fields |
No | agent.lessons.path must be a relative path inside the workspaceagent.skills.path must be a relative path inside the workspace |
workspace.auto_branch |
boolean |
true |
No | None |
workspace.cleanup_idle_ttl_ms |
integer |
86400000 |
No | must be greater than 0 |
workspace.cleanup_sweep_interval_ms |
integer |
600000 |
No | must be greater than 0 |
workspace.kind |
string |
"local_git" |
No | must be one of local_git, filesystem |
workspace.output_root |
string |
none |
No | None |
workspace.root |
string |
OS temporary directory + /detent_workspaces |
No | None |
workspace.source_root |
string |
none |
No | None |
Per-change security audit
gate.security_audit.enabled defaults to true for every tracker. The audit
reviews each immutable base/head before merge. P1/P2 findings send the change to
Rework when confirmed; non-blocking findings remain recorded on the Change or
pull request. A potentially P1/P2 lead that cannot be established from the
supplied evidence has certainty undetermined, no severity, and an explicit
unresolved fact. The same stage verifies it once using the effective model
selection's complex model in a fresh session, with all supplied evidence and
the original deadline and capacity reservation. Confirmed findings go to Rework;
disproved leads remain recorded as rejected with their reasoning. If verification
still cannot decide, native completion uses the existing Blocked human_action
park, naming the lead, both verdicts and the unresolved fact. Both audit verdicts
are retained on the Change. Prior unresolved findings remain blocking when their
context is absent; their unresolved severity cannot change.
Backend and runner failures are instance failures and do not become findings.
For tracker.kind: hub_native, audits run for both plain git delivery
(deliverable.github_pull_request: false, the default) and GitHub pull request
delivery (deliverable.github_pull_request: true). Migrating to the native
tracker requires no audit opt-in: keep the default or explicitly set
gate.security_audit.enabled: true in the approved project policy. GitHub pull
request delivery is not required for the audit.
The effective model selection's stages.security_audit selects the backend,
model and effort. Codex and Claude use their ordinary authentication, including
subscription and metered accounts. Each audit consumes provider tokens and
runtime, reported with other stage usage; metered accounts incur their usual
provider charges. Native direct landing also audits the integrated base/head
before push. A rebase, an earlier batch member or a concurrent base update
requires fresh evidence for the resulting commit and incurs another audit.
To disable it, explicitly set the existing project gate policy in detent.yaml
(or update that policy through the project's existing configuration API/MCP owner):
gate:
security_audit:
enabled: false
An explicit false wins over the default. Repository-owned native policy changes
still require approval through the existing trusted-revision policy owner.