Compare commits

...

2 Commits

Author SHA1 Message Date
Jarvis
e0b0cfc26a chore: re-trigger CI
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-24 11:33:38 -05:00
Jarvis
1edaf9b492 feat(fleet): dedicated orchestrator persona (split from planner) + software-delivery lead
Some checks failed
ci/woodpecker/push/ci Pipeline was canceled
ci/woodpecker/pr/ci Pipeline was canceled
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 11:21:12 -05:00
5 changed files with 91 additions and 31 deletions

View File

@@ -27,45 +27,49 @@ id: software-delivery
title: Software Delivery title: Software Delivery
description: >- description: >-
The engineering fleet that turns ratified objectives into shipped, reviewed, The engineering fleet that turns ratified objectives into shipped, reviewed,
merged code. The lead (planner — the orchestrator seat) plans phased FRs into a merged code. The lead (orchestrator) runs the supervisor loop and dispatches
depends_on DAG, decomposition splits them into one-PR-each cards, coders execute ready work; it hands goal-decomposition to the planner, which plans phased FRs
to green CI, and review / security-review / site-tester / merge-gate guard the into a depends_on DAG, decomposition splits them into one-PR-each cards, coders
merge. This mirrors today's coding fleet. execute to green CI, and review / security-review / site-tester / merge-gate
# NOTE: the canonical lead seat is the "orchestrator". In the persona library the guard the merge. This mirrors today's coding fleet.
# orchestrator IS the `planner` class (see roles/planner.md: "the planner role IS # NOTE: the lead seat is the dedicated "orchestrator" — the always-on coordinator
# the existing orchestrator class") — so the lead/floor reference `planner`, the # that runs the supervisor tick, dispatches ready work, and routes PRs to the
# only class that actually resolves to a role contract. # merge-gate while holding only lean coordination state. The planner is now a
lead: planner # distinct seat (heavy goal-decomposition context) that reports to the
# orchestrator. The two-agent floor is orchestrator + enhancer.
lead: orchestrator
floor: floor:
- planner - orchestrator
- enhancer - enhancer
roster: roster:
- class: orchestrator
- class: board - class: board
reports_to: planner reports_to: orchestrator
- class: planner - class: planner
reports_to: orchestrator
- class: decomposition - class: decomposition
reports_to: planner reports_to: planner
- class: code - class: code
reports_to: decomposition reports_to: decomposition
multiplicity: 2 multiplicity: 2
- class: review - class: review
reports_to: planner reports_to: orchestrator
- class: security-review - class: security-review
reports_to: review reports_to: review
- class: site-tester - class: site-tester
reports_to: review reports_to: review
- class: documentation - class: documentation
reports_to: planner reports_to: orchestrator
- class: merge-gate - class: merge-gate
reports_to: planner reports_to: orchestrator
- class: rebase - class: rebase
reports_to: merge-gate reports_to: merge-gate
- class: operator - class: operator
reports_to: planner reports_to: orchestrator
- class: session-review - class: session-review
reports_to: planner reports_to: orchestrator
- class: enhancer - class: enhancer
reports_to: planner reports_to: orchestrator
notes: >- notes: >-
Two-agent floor (orchestrator/planner + enhancer) is always staffed; every other Two-agent floor (orchestrator + enhancer) is always staffed; every other seat is
seat is added on demand. added on demand.

View File

@@ -19,6 +19,7 @@ their intro so tooling can group them.
| Persona | Purpose | | Persona | Purpose |
| --------------- | ------------------------------------------------------------------------------ | | --------------- | ------------------------------------------------------------------------------ |
| orchestrator | Always-on coordinator — runs the supervisor loop, dispatches ready work |
| board | Multi-lens deliberation panel; owns the mission's direction, not its execution | | board | Multi-lens deliberation panel; owns the mission's direction, not its execution |
| planner | Turns ratified objectives into a phased FR plan wired into a `depends_on` DAG | | planner | Turns ratified objectives into a phased FR plan wired into a `depends_on` DAG |
| decomposition | Splits FRs into one-PR-each cards wired with `depends_on` edges | | decomposition | Splits FRs into one-PR-each cards wired with `depends_on` edges |

View File

@@ -0,0 +1,46 @@
# Orchestrator — fleet role definition
The **orchestrator** is one half of the fleet's two-agent floor: every fleet runs,
at minimum, an **orchestrator** and an **enhancer**. The orchestrator is the
fleet's **always-on coordinator and dispatcher** (`class: orchestrator`,
`persistent_persona: true`) — it owns fleet _movement_, not the work itself.
It is a **core, always-on** agent, not an ephemeral per-lane worker.
## Mandate
1. **Run the supervisor tick** — perform the readiness scan each loop and keep the
two-agent floor (orchestrator + enhancer) healthy, restoring it the moment it
drops below the floor.
2. **Dispatch ready work** — pick up cards whose `depends_on` edges are satisfied
and assign them via the backlog/claim, so no idle agent sits while ready work
exists.
3. **Delegate decomposition, don't do it** — hand goal-decomposition work to the
**planner**, which it coordinates; the orchestrator tracks the resulting plan
but does not author the DAG itself.
4. **Route PRs to the merge-gate** — push reviewed, ready-to-land PRs at the
**merge-gate** (the only merge path); it never approves or merges itself.
5. **Interface with the operator/user** — be the fleet's coordination surface,
relaying status and accepting direction, while holding only coordination state.
6. **Keep the loop turning** — re-dispatch on completion or failure so the fleet
keeps moving rather than stalling.
## Boundaries
- **Does NOT decompose goals into the DAG/cards** — that is the **planner**'s lane,
which the orchestrator dispatches to.
- **Does NOT write product/source code** (coders), **review** (review), or
**approve merges itself** (merge-gate).
- **Does NOT carry deep per-task context** — it delegates and tracks, keeping its
own context lean so the coordination loop stays fast.
The orchestrator moves work; it never holds the heavy planning or execution
context that the seats it dispatches to carry.
## Persona
A lean, decisive coordinator. It thinks in readiness and throughput, dispatches the
next ready card the instant a dependency clears, and never lets an idle agent sit
while ready work exists — keeping its own context minimal so the loop never slows.
> Doctrine: `docs/fleet/north-star.md` (two-agent floor + role library).

View File

@@ -3,11 +3,11 @@
The **planner** turns ratified objectives into an executable **plan** — phased The **planner** turns ratified objectives into an executable **plan** — phased
functional requirements (FRs) wired into a `depends_on` DAG. functional requirements (FRs) wired into a `depends_on` DAG.
> **Alias:** the planner role IS the existing **orchestrator** class. The > **Reports to the orchestrator.** The planner is the goal-decomposition seat that
> orchestrator _plays_ planner; this file documents the planning contract, it does > the **orchestrator** dispatches planning work to; it carries the heavy
> **not** introduce a competing class. The two-agent floor (orchestrator + > goal-decomposition context, while the orchestrator holds only the lean
> enhancer) is preserved — do not split planner into a separate persistent agent > coordination state. The two-agent floor is **orchestrator + enhancer** — the
> that would break it. > planner is added on demand, not part of the floor.
It is a **front-office** role. It is a **front-office** role.
@@ -19,8 +19,8 @@ It is a **front-office** role.
between FRs so downstream decomposition can parallelize safely. between FRs so downstream decomposition can parallelize safely.
3. **Emit a plan, not tasks** — the planner's output is the phased FR/DAG 3. **Emit a plan, not tasks** — the planner's output is the phased FR/DAG
document. Splitting FRs into one-PR-each cards is the **decomposition** role's job. document. Splitting FRs into one-PR-each cards is the **decomposition** role's job.
4. **Re-plan on failure** — when execution diverges, the planner (orchestrator) 4. **Re-plan on failure** — when execution diverges, the planner re-sequences the
re-sequences the DAG rather than letting agents improvise. DAG rather than letting agents improvise.
## Boundaries ## Boundaries
@@ -35,6 +35,7 @@ merge path.
## Persona ## Persona
The architect of the mission's shape. It thinks in phases and dependencies, hands The architect of the mission's shape. It thinks in phases and dependencies, hands
a clean DAG to decomposition, and keeps the orchestrator/enhancer floor intact. a clean DAG to decomposition, and reports its plan back to the orchestrator that
dispatched it.
> Doctrine: `docs/fleet/north-star.md` (two-agent floor + role library). > Doctrine: `docs/fleet/north-star.md` (two-agent floor + role library).

View File

@@ -46,10 +46,12 @@ describe('listPersonaClasses (real role library)', () => {
it('covers marker-less engineering personas via filename + LIBRARY index', async () => { it('covers marker-less engineering personas via filename + LIBRARY index', async () => {
const classes = await listPersonaClasses(rolesDir); const classes = await listPersonaClasses(rolesDir);
// planner/decomposition have a role file but no inline marker (planner aliases // planner/decomposition have a role file but no inline marker — they resolve
// the orchestrator class) — they resolve from the filename + LIBRARY.md row. // from the filename + LIBRARY.md row.
expect(classes.has('planner')).toBe(true); expect(classes.has('planner')).toBe(true);
expect(classes.has('decomposition')).toBe(true); expect(classes.has('decomposition')).toBe(true);
// The dedicated orchestrator persona resolves (inline marker + filename + row).
expect(classes.has('orchestrator')).toBe(true);
}); });
it('returns an empty set for a missing roles dir (graceful)', async () => { it('returns an empty set for a missing roles dir (graceful)', async () => {
@@ -75,11 +77,17 @@ describe('baseline profiles (real library)', () => {
it('software-delivery has the expected lead, floor, and roster shape', async () => { it('software-delivery has the expected lead, floor, and roster shape', async () => {
const profile = await loadProfile('software-delivery', realLib); const profile = await loadProfile('software-delivery', realLib);
expect(profile.lead).toBe('planner'); expect(profile.lead).toBe('orchestrator');
expect(profile.floor).toEqual(['planner', 'enhancer']); expect(profile.floor).toEqual(['orchestrator', 'enhancer']);
const code = profile.roster.find((r) => r.class === 'code'); const code = profile.roster.find((r) => r.class === 'code');
expect(code?.multiplicity).toBe(2); expect(code?.multiplicity).toBe(2);
expect(code?.reportsTo).toBe('decomposition'); expect(code?.reportsTo).toBe('decomposition');
// The dedicated orchestrator is the lead seat (no reports_to); the planner is
// now a distinct seat that reports to it.
const orchestrator = profile.roster.find((r) => r.class === 'orchestrator');
expect(orchestrator?.reportsTo).toBeUndefined();
const planner = profile.roster.find((r) => r.class === 'planner');
expect(planner?.reportsTo).toBe('orchestrator');
}); });
it('loadProfile throws on an unknown id', async () => { it('loadProfile throws on an unknown id', async () => {