Skip to content
Docs Operate Detent Mirrored upstream
retrospection.md
Docs source v0.57.0 154392918736 View this file at the pinned commit ↗

Efficiency retrospection

Back to README

Each project can opt into an evidence-based reflection loop over its runtime telemetry. The pass runs after agent completions and on a daily schedule, groups recurring inefficiency patterns, and creates or updates fingerprinted board issues for human triage.

retro:
  enabled: true
  schedule: "0 3 * * *"
  target_state: Backlog
  labels: [retro]
  product_repository: digitaldrywood/detent
  daily_issue_cap: 3
  lookback_days: 7
  min_occurrences: 2
  single_occurrence_severity: critical
  fallback_threshold: 3
  receipt_baseline_multiple: 4

Workflow findings stay on the project's board and include a governed proposed WORKFLOW.md change when the evidence maps to a known setting. Product findings such as completed-work re-dispatch or systemic capacity handling route to product_repository. The configured retro label is always retained, and target_state: Todo can be used when a project explicitly wants findings to skip Backlog triage.

Detent files a finding after min_occurrences, or after one occurrence at or above single_occurrence_severity. daily_issue_cap limits newly created issues; recurrence updates to existing fingerprinted issues remain allowed. Retrospection never edits workflow files, prompts, or runtime configuration. Its issue body records the evidence, proposed change, and pending human outcome. detent doctor reports the last run, finding count, and filed/updated issue counts for every enabled project. ProjectV2 trackers must set tracker.repository so Detent knows where to create project-level findings.

gate controls the validation contract the agent and operator flow follow. Omitting it preserves the code default: kind: command with run: make check, plus green CI, no P1 automated PR review findings, a quiet window, and a current-head automated PR review before auto-promotion. Set automated_review: optional to wait through agent.auto_promote.gate_wait_timeout_seconds and then continue to Merging when the remaining checks pass. Set automated_review: off to skip that wait. The legacy require_automated_review boolean remains accepted and maps true to required and false to off. The quiet period resets on observed issue updates, Project status updates, automated PR review submission, and linked PR activity such as a fresh push to the PR head. Failed or cancelled current-head CI moves a Human Review item back to Rework by default. Set ci_failure_action: skip only when red CI should park in Human Review; pending CI stays parked. Use kind: human_review with approval_label only when the workflow explicitly requires a human approval label to promote. Set required_status_checks to the exact branch-protection or ruleset check names that are release-blocking for the project. Detent treats a configured required check as non-green when it is missing, skipped, failed, cancelled, neutral, or still running on the current PR head. For required workflows that run only on a pull_request labeled event, set ci_trigger_label to that label, such as ci:ready. Detent then instructs implementation and rework workers to run the host-coordinated detent ci-trigger-label command after every head-changing push before they wait for current-head checks, passing the configured GitHub tracker host for Enterprise installations. Generated commands encode label and host arguments without shell-specific quoting. Detent records successful worker pushes and reapplies the configured label at completion when the worker did not leave it as the final CI trigger after the latest push. This preserves label ordering when a project uses additional CI lane labels. Merging workers also reapply it immediately after deterministic head-changing pushes and whenever current-head hydration reports required checks as missing, including after a rebase or another merge advances the base. Trigger events use a shared lock and persisted timestamp per repository and are spaced by ci_trigger_label_stagger_seconds (a positive value, default 15) to avoid a self-hosted CI stampede. detent doctor warns when it finds a label-gated required check without this setting.

Set gate.validator.enabled: true to add a validator-agent review before auto-promotion. The validator inspects the PR diff against the issue acceptance criteria and returns a structured verdict, score, summary, and severity-tagged findings. gate.validator.model optionally overrides the selected validator route model, min_score below threshold routes to Rework, and any finding severity listed in block_on routes to Rework regardless of score. Validator production failures are retried with backoff up to max_attempts (default 3). Each failure is logged and visible to detent doctor; an exhausted validator routes the item to Rework with the failure cause. For Codex-backed validators, start with the cheap-tier override gate.validator.model: gpt-5.4-mini and watch rework-rate per validator model once cache/model telemetry lands. gate.validator.max_inline_diff_bytes defaults to 65536; validator prompts include the full diff only at or below that size and otherwise seed stat-only context.

plan controls the optional plan-approval stop before implementation. It is disabled by default, preserving the direct dispatch behavior. When enabled, the first Todo dispatch runs in plan-only mode, posts a ## Detent Plan issue comment, and moves the issue to the configured stop such as Plan Review. review: human waits for approval_label (plan-approved by default), review: automated waits for a ## Detent Plan Review issue comment or current-head automated review state, and review: both accepts either path. Blocking P1 plan findings route the issue to Rework with feedback.

For production, self-hosted, or multi-instance GitHub Projects, prefer GitHub App installation authentication instead of a shared personal access token. App installation tokens have a dedicated GraphQL budget per installation and scale with larger installations, while a PAT shares one fixed user budget across Detent, agents, and operator gh calls. Configure the tracker with github_app_id, github_app_installation_id, and either github_app_private_key or github_app_private_key_path; keep api_key for small local setups or one-off evaluation.

Default workflows do not need worktree setup hooks. Detent creates and removes Git worktrees natively, so a fresh Windows project can dispatch without bash. Omit codex.shell and hooks.shell to use the per-OS defaults: sh on Unix and cmd on Windows. For portable hooks, prefer no hook when Detent already does the setup natively. When a hook is necessary, keep it to commands available on every target or set hooks.shell: pwsh and write PowerShell that reads Detent values from $env:WORKSPACE, $env:WORKSPACE_KEY, $env:BRANCH, and $env:ISSUE_IDENTIFIER. The older DETENT_* hook variables remain available as deprecated aliases for one release.

  1. Create the global config and add the project:
detent init
detent add-project \
  --id <id> \
  --workflow /absolute/path/to/project-checkout/WORKFLOW.md \
  --workdir /absolute/path/to/project-checkout

For first-time onboarding, leave --workflow-ref unset until this WORKFLOW.md has been merged to the ref Detent should read from. detent doctor validates the configured ref; setting --workflow-ref origin/main before origin/main:WORKFLOW.md exists will fail even when the file exists locally in the working tree. After the first workflow merge, add workflow_ref: origin/main to the project entry when you want Detent to load the workflow from the branch tip instead of the working-tree file.

Edit the resolved global.yaml and set the top-level runtime keys:

env: prod
log_level: info
github_token: gh
port: 4000
update:
  auto_check_enabled: true
  check_interval_hours: 24
  auto_apply_enabled: false
  1. Verify the setup before dispatching:

    detent doctor --allow-write-probes
    

detent doctor is a preflight check: config resolution, the SQLite database, the codex binary, GitHub auth mode, GitHub tracker readiness, git, and whether the server port is free. It also reports each project's active definition root, layout, revision, authority files, local overlays, and whether the running process is stale. In ProjectV2 mode it checks project access, Status options, board item reads, repository issue/PR access, and rate-limit visibility. In issue-field mode it checks repository access, issue field discovery, Status option discovery, issue reads by field value, and REST/GraphQL rate-limit visibility. In label mode it checks repository access, status label mappings, issue reads by configured status labels, and REST/GraphQL rate-limit visibility. By default doctor is read-only: if a configured workflow would run write probes, the report warns that they were skipped. Pass --allow-write-probes only after the onboarding mutation gate has passed and the operator has explicitly confirmed mutation. With that flag, ProjectV2 and issue-field modes require tracker.write_probe_issue when integration needs status-write proof, because their status mutations target a concrete project item or issue field value. Label mode does not need a persistent status-labeled scratch issue for the default permission proof: doctor sends intentionally invalid repository-label and issue-create requests, expecting GitHub to reject them with validation while proving the token has the repository Issues write permission class. Configure tracker.write_probe_issue in label mode only for legacy/deep issue-object proof, such as reapplying an existing status label on a scratch issue. That proof is stronger for the chosen issue object, but the issue must be kept off the board by removing Detent status labels or closing it after migration. Before starting Detent, fix any FAIL (missing github_token: gh or an unauthenticated codex are the usual culprits). If Detent is already running on the configured port, the server-port check can fail because the live service owns the port; use detent doctor --port 0 for the same read-only config, toolchain, token, and database preflight without the port collision, or detent doctor --port 0 --allow-write-probes after mutation authorization to prove writes, then verify the live service with /health.

For Detent dogfood/self-tests that need a running server, start an isolated mock runtime instead of stopping or reusing the live process on 127.0.0.1:4000:

detent dev-runtime --port 0

The command prints Mode: isolated dev runtime, the selected dashboard URL, temp home, DB mode, tracker mode, and fixture path. By default it uses a temp config/workspace home, an in-memory SQLite database, a stateful fixture-backed memory tracker, and a fake runner; it does not call GitHub or mutate a real ProjectV2 board. It refuses the live dogfood port and live ~/.detent/detent.db unless explicitly overridden.

Use the built-in Kanban demo when you want to evaluate the operator board and mutation dialogs without a GitHub token, a real ProjectV2 board, or production database state:

detent dev-runtime --demo kanban --port 0

Pass --demo-project to choose the generated project ID when you want generic demo URLs and labels instead of the default dogfood-safe ID:

detent dev-runtime --demo kanban --demo-project demo-project --port 0

Demo runtimes bind to 0.0.0.0 when --host is omitted so the selected random port can be reached from trusted network interfaces. From another machine on Tailscale, replace the local banner host with the Tailscale hostname. With the override above, open http://prometheus:<port>/kanban for the mixed-project board or http://prometheus:<port>/projects/demo-project/kanban for the generated project's interactive board. Pass --host 127.0.0.1 for a local-only demo run.

The Kanban demo keeps the runtime isolated on the memory tracker, seeds at least four projects with one or two cards each, and mixes configured project colors with deterministic automatic colors. The fleet /kanban board is read-only and shows cards across those projects; project-specific pages such as /projects/demo-project/kanban enable integration mode for the generated demo workflow. The demo includes explicit server.kanban.allowed_transitions such as Backlog -> Todo so sheet-based Move actions can be exercised without weakening production defaults. Demo cards cover Backlog, Todo, In Progress, Blocked, Human Review, Rework, Merging, Done, and Cancelled states, including issue-only cards, linked PR cards, CI pass, pending, and failure states, Codex review clean and finding states, labels, assignees, blockers, and wait metadata. Issue and PR comments are captured by the memory connector with no external side effects.

Use the screenshots demo when you need deterministic pages, HTMX fragments, API responses, reports, and SSE payloads for documentation screenshots, video recording, or visual e2e baselines:

detent dev-runtime --demo screenshots --port 0

The screenshots demo uses the same isolation model and demo bind default as the Kanban demo: memory tracker, fake runner, isolated home, isolated database, isolated workspaces, fake https://github.test/... URLs, no GitHub calls, no real ProjectV2 mutation, and no live dogfood port by default. It freezes demo time at 2026-06-15T12:00:00Z unless started with --demo-clock play, which advances SSE ticks and visible running-work counters for video capture. The boot banner prints the scenario manifest location. Screenshots mode intentionally keeps the primary project fixed at dogfood so page routes and visual baselines remain deterministic:

Scenario manifest: /api/v1/demo/scenarios

Select a scenario with X-Detent-Demo-Scenario; the visible URL stays on the normal page route:

const scenarios = [
  ["fleet-healthy-parallel-work", "/"],
  ["fleet-kanban-multiproject", "/kanban"],
  ["kanban-full-integration", "/projects/dogfood/kanban"],
  ["reports-normal-window", "/reports"],
];

for (const [scenario, route] of scenarios) {
  await page.setExtraHTTPHeaders({ "X-Detent-Demo-Scenario": scenario });
  await page.goto(`${baseURL}${route}`);
  await page.waitForLoadState("networkidle");
  await expect(page).toHaveScreenshot(`${scenario}.png`);
}

For visual comparisons, keep the screenshot environment stable: browser, viewport, fonts, OS rendering, device scale factor, and generated assets should match the baseline environment. The manifest includes each scenario ID, route, required header, recommended viewport, screenshot name, and wait selector. A quick JSON smoke check looks like this:

curl -H 'X-Detent-Demo-Scenario: fleet-healthy-parallel-work' "$DETENT_URL/api/v1/state"

Use the capture harness when you need the canonical video-production artifact set from one command:

detent dev-runtime capture --out ./capture

The harness starts an isolated screenshots demo on an ephemeral local port, loads the scenario manifest, captures the canonical still set, and writes a deterministic terminal onboarding cast. It does not read or write the operator's real ~/.config/detent/global.yaml. Stable output paths are:

capture/demo-capture-v1.json
capture/stills/v1/01-fleet-healthy-parallel-work.png
capture/stills/v1/02-fleet-kanban-multiproject.png
capture/stills/v1/03-kanban-full-integration.png
capture/stills/v1/04-project-active-overview.png
capture/stills/v1/05-reports-normal-window.png
capture/stills/v1/06-onboarding-project-selection.png
capture/terminal/v1/onboarding.cast

By default the browser viewport is 1920x1080 with --device-scale-factor 2, producing 4K PNGs. Pass --scenario <id> one or more times for a named subset, --all-scenarios for every browser-capturable GET scenario in the manifest, --width, --height, and --device-scale-factor for alternate framing, or --demo-clock play when a motion capture needs advancing counters. The PNG capture uses a local Chrome-family browser; pass --browser <path> or set DETENT_CAPTURE_BROWSER when auto-detection cannot find one.

The CI browser visual gate runs Playwright when a PR changes UI-sensitive paths such as .github/workflows/ci.yml, go.mod, go.sum, package.json, static/**, internal/web/**, internal/cli/dev_runtime*.go, internal/devruntime/**, Templ inputs, or screenshot/onboarding docs. It builds the PR's Detent binary, starts isolated dev-runtime instances on port 0, captures current evidence under tmp/playwright-evidence, and uploads Playwright reports, traces, screenshots, and image diffs when assertions fail. PRs without UI-sensitive changes keep the required Browser Visual check fast by building the Detent binary and running a CLI smoke instead of starting Playwright.

Run the layout gate locally after installing Playwright's Chromium browser:

npx playwright install chromium
make visual-e2e

Committed image baselines are authoritative for GitHub Actions Ubuntu x64/Chromium. On non-Linux hosts, make visual-e2e still runs the browser layout assertions and captures evidence, but skips pixel comparison unless DETENT_VISUAL_STRICT=1 is set.

Update baselines only when the visual change is intentional. Run the update in the same Ubuntu x64/Chromium environment as CI, then review and commit the changed files under tests/visual/__screenshots__/chromium/:

make visual-e2e-update

Do not commit tmp/playwright-evidence, tmp/playwright-report, or tmp/playwright-results; those are transient review and debugging artifacts.

Use the normal live runtime, detent with your global config, only when you intend to operate on the configured tracker and ProjectV2 board. Use detent dev-runtime --fixture <path> for focused fixture validation such as autopromote behavior, --demo kanban for safe board exploration, and --demo screenshots for deterministic page-addressable screenshots.

  1. Start Detent:
detent

Open the dashboard at http://localhost:4000. Use --host and --port to override the address. Before exposing a remote URL such as http://prometheus:4000/, choose the dashboard bind mode:

On shutdown, the first Ctrl-C stops new dispatches and drains running agent sessions while the terminal reports the blocker count and time remaining. A second Ctrl-C force-quits immediately, interrupts those sessions, and re-queues their issues. A new process using the same runtime database will fail with an actionable error until the prior process has released it; a listener conflict also fails before SQLite migrations begin.

  • 127.0.0.1 keeps the dashboard local to the host and is the safest default for SSH tunnel access.
  • A specific private or Tailscale IP exposes the dashboard only on that interface and is preferred for VPN-only access.
  • 0.0.0.0 exposes the dashboard on every interface, not just Tailscale. Use it only on trusted private networks with the expected host firewall rules.

When Detent is bound to 127.0.0.1, curl from the same host can work while http://<host>:4000/ fails from another machine because loopback is not reachable remotely. Set server.host in detent.yaml for the default bind, or set --host in the CLI command or service ExecStart:

detent --host 127.0.0.1 --port 4000
detent --host <tailscale-or-private-ip> --port 4000
detent --headless --host 0.0.0.0 --port 4000

Verify the listener and the local or VPN URL you intend operators to use:

ss -ltnp | rg ':4000|detent'
curl -fsS http://127.0.0.1:4000/api/v1/state
curl -fsS http://<tailscale-or-private-ip>:4000/api/v1/state