← Articles

// FIELD NOTE

My AI Orchestrator Is Dumb on Purpose

Cory LaNou

Cory LaNou

My AI Orchestrator Is Dumb on Purpose

My AI Orchestrator Is Dumb on Purpose

Overview

Most people assume the AI system running my backlog must be smart. It isn't, and I built it dumb on purpose. Detent moves real work from a backlog to a merged pull request without anyone babysitting it, but the orchestrator itself knows nothing about software. Every bit of intelligence lives in files I wrote, in plain text, for each project. In the video I open the actual files from my production projects — no mockups — and this post is the deep-dive version, with the real config excerpts inline.

Two layers, split on purpose

Detent is a single Go binary that treats a status board as a state machine. An issue moves from To Do, to In Progress, to review, to merged, and that status is the source of truth. The configuration splits into two layers. One global config runs the host, and one workflow file defines each project. The global config decides how much runs at once. The workflow file decides what good engineering looks like for that repo.

That split is deliberate. The orchestrator stays a dumb traffic cop, and all the judgment lives in files you own, version, and review like code.

The global config — the real file

This is my actual global config. It fits on one screen, and that's the point.

# ~/.config/detent/global.yaml — the whole host, one screen
global:
    max_concurrent_agents: 5
    scheduling: weighted
    fair_share:
        half_life: 1h
    startup:
        jitter_seconds: 10
        max_spawn_per_second: 2

It caps the whole machine at five concurrent agents, no matter how many projects are asking for work. Scheduling is weighted, with a fair-share half life of one hour, so one busy project can't starve the others. Startup gets ten seconds of jitter and a cap of two spawns per second, because five agents slamming GitHub at the same instant is how you eat your rate limit.

Then comes the project list. Each entry is just an ID, a path to the workflow file, a working directory, a weight, and a priority. Client work is ranked ahead of my own site, so paying projects get dispatched first when everything wants attention.

projects:
    - id: digitaldrywood
      workflow: /home/corylanou/projects/digitaldrywood/digitaldrywood/WORKFLOW.md
      workdir: /home/corylanou/projects/digitaldrywood/digitaldrywood
      weight: 1
      priority: 3
    - id: creswoodcorners-phone
      workflow: /home/corylanou/projects/digitaldrywood/creswoodcorners-phone/WORKFLOW.md
      workdir: /home/corylanou/projects/digitaldrywood/creswoodcorners-phone
      weight: 1
      priority: 2
    - id: digitaldrywood-video
      workflow: /home/corylanou/Dropbox/Digital Drywood/Video Production/WORKFLOW.md
      workdir: /home/corylanou/Dropbox/Digital Drywood/Video Production
      weight: 1
      priority: 2

Notice what's missing. There is nothing in this file about tests, reviews, branches, or how to write software. The host schedules work, and that is all it does.

One file, two halves

Every project points at one workflow file, and that file has two halves. The top half is machine config — the tracker binding, the board states, the workspace rules, the agent limits, and the validation stages. Below that sits a prose contract: the prompt every agent receives, written in plain English, describing exactly how work happens in this repo. The config wires the system to your board, and the prose teaches the agent your engineering process. The whole thing is one file, checked into the repo and reviewed like any other change.

The tracker and the states

A project can bind to more than one kind of board. Detent speaks Linear, GitHub Projects, plain repository labels, or no board at all in its own native mode, where all the state stays local on SQLite with no API limits — that's how the pipeline behind the video itself runs. My site runs in label mode, where plain repository labels carry the status, so the board never leaves GitHub.

# WORKFLOW.md — the tracker binding: plain GitHub labels carry the status
tracker:
  kind: github
  github_status_source: label
  repository: digitaldrywood/digitaldrywood
  status_label_prefix: "detent:"
  active_states:
    - Todo
    - In Progress
    - Rework
    - Merging
  observed_states:
    - Backlog
    - Human Review
    - Blocked
  terminal_states:
    - Done
    - Cancelled

The states split into three groups, and that grouping is the safety model. Active states are where agents actually run. Observed states are watched but never dispatched. Terminal states end the story. An agent physically cannot pick up work from Backlog, because Backlog is not an active state.

Dependencies are handled in config too. If an issue declares that it's blocked by another issue, Detent parks it and watches the blocker. The moment the blocker merges, the issue moves itself back to To Do. Nobody wakes up at night to unblock a queue.

# blocked issues watch their blocker and release themselves
dependency_auto_unblock:
  enabled: true
  source_states:
    - Blocked
  target_state: Todo
  readiness: terminal_or_merged

Worktrees and hooks

The workspace section is where isolation comes from. Every issue gets its own git worktree, on its own branch, created from my source checkout. Five agents can work in parallel because they never share a directory. Nothing can step on the main checkout, and nothing can step on a sibling. Idle worktrees clean themselves up after a day, and a sweep runs every ten minutes to enforce it.

# every issue gets its own git worktree, on its own branch
workspace:
  root: /home/corylanou/code/digitaldrywood-detent-workspaces
  source_root: /home/corylanou/projects/digitaldrywood/digitaldrywood
  auto_branch: true
  cleanup_idle_ttl_ms: 86400000      # idle worktrees expire after a day
  cleanup_sweep_interval_ms: 600000  # sweep every ten minutes

Hooks exist so every fresh worktree comes up with a valid environment. When a worktree is created, my after-create hook copies the environment file over, allows it with direnv, and installs node modules if they're missing.

hooks:
  timeout_ms: 600000
  after_create: |
    SOURCE_REPO=/home/corylanou/projects/digitaldrywood/digitaldrywood
    git -C "$SOURCE_REPO" fetch origin main -q 2>/dev/null || true
    git -C "$SOURCE_REPO" worktree prune >/dev/null 2>&1 || true
    if [ -f "$SOURCE_REPO/.envrc" ] && [ ! -f .envrc ]; then
      cp "$SOURCE_REPO/.envrc" .envrc
    fi
    if command -v direnv >/dev/null 2>&1 && [ -f .envrc ]; then
      direnv allow . >/dev/null 2>&1 || true
    fi
    if [ -f package-lock.json ] && [ ! -d node_modules ]; then
      npm ci
    fi

That's project knowledge encoded where it belongs. Detent has no idea what my environment variables are, and it shouldn't. It just runs my hook.

Agent limits and the merge train

The agent section sets the local limits — five concurrent agents, every run capped at twenty turns. One line in this section carries more weight than the rest: the Merging state is capped at exactly one agent. That single line is the merge train. Only one pull request rebases, watches CI, and merges at a time, so parallel branches never invalidate each other's green build.

agent:
  max_concurrent_agents: 5
  max_turns: 20
  max_concurrent_agents_by_state:
    Merging: 1        # <- the merge train. One PR rebases and merges at a time.
  dispatch_priority_by_state:
    - Merging
    - Rework
    - In Progress
    - Todo
  dispatch_priority_by_label:
    - priority
    - next-up
    - enhancement
    - content-ready
  auto_promote:
    enabled: true
    quiet_seconds: 0
    optout_label: requires-human-review

Dispatch priority is explicit — Merging beats Rework, Rework beats In Progress, and labels I chose can jump an issue up the queue. Auto-promotion is on, and there's an opt-out: any issue labeled requires-human-review will sit and wait for me, because some changes should never merge themselves.

The gates are your stages

I'd rather describe a gate as a stage a senior engineer already refuses to skip. You reproduce the problem before touching code, you plan the change, you write it with tests, you get it reviewed, and you prove it end to end. A gate is just a stage that must pass before the work advances — the same process you already believe in, enforced every single time.

gate:
  kind: command
  run: make check          # full build, generated templates, linters, tests
  require_automated_review: false
  ci_failure_action: rework  # red CI on the PR head -> straight to Rework

For my site, the validation command is make check, and that runs the full build, the generated templates, the linters, and the test suite. If CI fails on the pull request head, the config moves the issue to Rework instead of letting it rot. An agent picks it back up, reads the feedback, and fixes it.

The quiet period is a dial. My site sets it to zero and promotes immediately, but you can make every pull request soak until activity settles before it advances. You can go further: there's an optional validator agent that reviews the pull request and blocks promotion below a score, and there's a mode that waits for a human approval label. The gate is a policy you choose, not a default you inherit.

The prose contract

Under the config sits the contract, and this is the half that does the heavy lifting. It opens by templating in the issue — the title, the current state, the labels, and the full description — so the agent starts every run knowing exactly what it's working on. Then it lays down authority: the agent must read the project's own instruction files first, and when rules conflict, the stricter rule wins.

It demands a workpad — one persistent issue comment that keeps the plan, the acceptance criteria, the validation evidence, and the handoff notes together:

Keep a single persistent GitHub issue comment headed `## Codex Workpad`. Use it
for the plan, acceptance criteria, validation evidence, blockers, and handoff
notes. Do not scatter progress across multiple comments.

The workpad also keeps a machine-readable status block. The agent sets it to in progress, blocked, or complete, and Detent reads the block — never the prose.

The contract walks every issue through the stages themselves:

5. Reproduce or confirm the requested behavior before changing code when the
   issue is a bug or behavior change.
6. Implement the smallest complete change.
7. Run focused validation and tests required by `AGENTS.md`, `CLAUDE.md`, and
   the issue.
8. Run `make check` from the repo root.
9. Run pre-commit checks and confirm they pass. Treat any failing hook as
   blocking; never bypass hooks to create the commit.
10. Commit and push a branch.
11. Open or update a GitHub PR with concrete summary and validation details.

It even handles scope creep. If the agent finds real work outside the issue, the contract says to open a new issue in Backlog rather than expanding this one. None of that is Detent being clever. Those sentences came out of my head, from years of doing this the hard way, and now they run on every issue in parallel.

Same binary, different cultures

The customization really shows up when you put two projects side by side. My site validates with make check. Creswood Corners, a client project, validates with the test suite plus a full Go build, because that repo defines done differently. My site runs the agent with the sandbox wide open, because it's my machine and I accept that trade. The client project locks it down to workspace writes. On my site, red CI moves the issue straight to Rework. On the client project it just sits and waits, because I look at those failures myself.

# digitaldrywood (my site): my machine, my risk tolerance
codex:
  thread_sandbox: danger-full-access
gate:
  kind: command
  run: make check
  ci_failure_action: rework   # red CI goes straight back to an agent
# creswoodcorners (client work): locked down, failures wait for me
codex:
  thread_sandbox: workspace-write
gate:
  kind: command
  run: make test && go build ./...
  ci_failure_action: skip     # red CI sits until a human looks

Even the dispatch politics differ — the client board moves bug and operations labels to the front, while my site gives content work the fast lane. It's the same binary and the same board shape, running two completely different engineering cultures. The system didn't decide any of that. The workflow files did.

Backlog to Done, no babysitting

Watch how the whole thing moves. An issue starts in Backlog, where it's visible but untouchable. I write the spec — the scope, the acceptance criteria, and the tests I expect — and I move the card to To Do. That is my entire involvement in starting the work.

Detent claims it, cuts a worktree, runs my bootstrap hook, and hands the agent the contract with the issue templated in. The agent works the stages, opens a pull request with the validation evidence attached, and sets its workpad status to complete. Checks go green on the head commit, the issue promotes to Merging, and the train takes over one candidate at a time. The rebase lands, CI passes on the final head, the pull request merges, and the issue moves to Done.

I was not sitting there for any of it. I reviewed a spec at the start and a pull request at the end, which are the two places human judgment actually belongs.

Why dumb wins

Anything smart the runtime did on its own would be a decision I never made and can't version. Instead, the intelligence stays in my spec and the runtime supplies the discipline. Every rule I believe in runs on every issue, every time, without me repeating myself.

That is the difference between this and vibe coding. Nothing merges here on vibes. It merges because the stages I defined passed, with the evidence written down. When a better model comes out next year, I swap the model. The process stays, because the process was never in the model to begin with.

You don't have to write any of this

You are not required to know any of this to get started. Detent's setup is built to be run by your agent. It follows the setup docs, it asks you how you work, and it takes the best path through. Maybe that's a GitHub Projects board. Maybe it's plain repository labels like mine. Or maybe it's no tracker at all, with Detent running fully local on its own SQLite database and giving you the same Kanban board feel. Every option in this post gets written for you, and you tune it from there. The files are the contract, but the setup is a conversation.

If you want to read these files yourself, Detent's own orchestration config is public — it dispatches the agents that build Detent itself. And I'd like to know where your workflow file would differ from mine: which stage would you never let an agent skip? Drop it in the comments on the video.

The repo and the docs are on GitHub. Point your agent at it and let it onboard itself.

Want more AI development insights?

Subscribe to the newsletter for weekly tips on using AI in professional development.

Subscribe to Newsletter

// KEEP READING

More articles