Strictly serial task processing with per-request git branches
Status: Accepted · Date: 2026-05-30 (aggregation/summary follow-up 2026-05-31) · Area: Agents
Context
The Orbit agent plans a request into worker tasks and executes them against the solution repository. Before 2026-05-30, each HTTP request ran its own serial loop inside the team lead — tasks were serial within a request but ran in parallel across concurrent requests, with multiple executors mutating one working tree. The agent also needed a defensible git story: partial work must not leak into the base branch, and a crash must not leave the tree in an ambiguous state.
Decision
- Execution is strictly one task at a time across all requests, owned by
a single process-wide
TaskProcessor.TeamLeadonly plans and enqueues (batched task creation, thenkick()); it no longer executes or commits. - Kick-on-enqueue, no polling timer. Producers call
kick(); a double-check after the drain clears the running flag (re-checkhasPending(), re-kick) closes the race where a task enqueued in the gap would be stranded — that check is load-bearing. - Per-request git branch lifecycle: a branch is created lazily from base
on a request’s first task;
workspace/is committed per task (also on failure, keeping the tree clean); when all tasks complete, the branch is merged--no-ffinto base, pushed, and deleted; on any failure, cancel, or merge conflict the request fails and the branch is kept unmerged with the working tree returned to base. Confirmed decisions: a failure cancels the request’s pending siblings; merge only on all-completed (a mid-failure multi-task request will not auto-merge after a single-task continue); branches are pushed for visibility. - The base branch is persisted at
.orbit/base-branchon first clean boot, so crash recovery on areq-*branch cannot mistake it for base. Startup recovery commits the interrupted task’s partial work, marks it failed, finalizes the request, returns to base, and drains the rest. - The pipeline has exactly two LLM boundaries, both via the SDK’s
structured output: planning (with one corrective retry on malformed
output) and request aggregation on the success path; the processor owns the
deterministic summary backbone (
.orbit/requests/<id>/summary.yaml).
Consequences
- One writer, well-defined settled moments — the invariant that later made the in-memory solution model and its reload hooks safe (ADR-007), and that pairs with the single-replica cap in ADR-004.
- Throughput is deliberately bounded: requests queue behind each other.
- Resume needs no separate code path — a task with a persisted
sdkSessionIdcontinues, one without starts fresh. - A historical note: this work gitignored
.orbit/so branches carry only solution deliverables. The three-repo split supersedes that —.orbit/becomes tracked in the internal repo, and the gitignore/untrack mechanism is to be removed (ADR-006).
Evidence
implementation/maxq-orbit-agent/codebase/src/orchestrator/TaskProcessor.ts,TeamLead.ts,TaskList.ts— processor, planner, index locking.orbit/base-branchand.orbit/requests/<id>/summary.yamlin a solution’s internal working statememory/orbit-agent-task-processor.md(invariants and confirmed decisions),memory/repo-split.md(the superseded.orbitgitignore)