From 1edaf9b49204aa31c2c15db461c941dbcacda8c0 Mon Sep 17 00:00:00 2001 From: Jarvis Date: Wed, 24 Jun 2026 11:17:46 -0500 Subject: [PATCH] feat(fleet): dedicated orchestrator persona (split from planner) + software-delivery lead Co-Authored-By: Claude Opus 4.8 --- .../fleet/profiles/software-delivery.yaml | 42 +++++++++-------- .../mosaic/framework/fleet/roles/LIBRARY.md | 1 + .../framework/fleet/roles/orchestrator.md | 46 +++++++++++++++++++ .../mosaic/framework/fleet/roles/planner.md | 17 +++---- .../src/commands/fleet-profiles.spec.ts | 16 +++++-- 5 files changed, 91 insertions(+), 31 deletions(-) create mode 100644 packages/mosaic/framework/fleet/roles/orchestrator.md diff --git a/packages/mosaic/framework/fleet/profiles/software-delivery.yaml b/packages/mosaic/framework/fleet/profiles/software-delivery.yaml index 105c55f..d010418 100644 --- a/packages/mosaic/framework/fleet/profiles/software-delivery.yaml +++ b/packages/mosaic/framework/fleet/profiles/software-delivery.yaml @@ -27,45 +27,49 @@ id: software-delivery title: Software Delivery description: >- The engineering fleet that turns ratified objectives into shipped, reviewed, - merged code. The lead (planner — the orchestrator seat) plans phased FRs into a - depends_on DAG, decomposition splits them into one-PR-each cards, coders execute - to green CI, and review / security-review / site-tester / merge-gate guard the - merge. This mirrors today's coding fleet. -# NOTE: the canonical lead seat is the "orchestrator". In the persona library the -# orchestrator IS the `planner` class (see roles/planner.md: "the planner role IS -# the existing orchestrator class") — so the lead/floor reference `planner`, the -# only class that actually resolves to a role contract. -lead: planner + merged code. The lead (orchestrator) runs the supervisor loop and dispatches + ready work; it hands goal-decomposition to the planner, which plans phased FRs + into a depends_on DAG, decomposition splits them into one-PR-each cards, coders + execute to green CI, and review / security-review / site-tester / merge-gate + guard the merge. This mirrors today's coding fleet. +# NOTE: the lead seat is the dedicated "orchestrator" — the always-on coordinator +# that runs the supervisor tick, dispatches ready work, and routes PRs to the +# merge-gate while holding only lean coordination state. The planner is now a +# distinct seat (heavy goal-decomposition context) that reports to the +# orchestrator. The two-agent floor is orchestrator + enhancer. +lead: orchestrator floor: - - planner + - orchestrator - enhancer roster: + - class: orchestrator - class: board - reports_to: planner + reports_to: orchestrator - class: planner + reports_to: orchestrator - class: decomposition reports_to: planner - class: code reports_to: decomposition multiplicity: 2 - class: review - reports_to: planner + reports_to: orchestrator - class: security-review reports_to: review - class: site-tester reports_to: review - class: documentation - reports_to: planner + reports_to: orchestrator - class: merge-gate - reports_to: planner + reports_to: orchestrator - class: rebase reports_to: merge-gate - class: operator - reports_to: planner + reports_to: orchestrator - class: session-review - reports_to: planner + reports_to: orchestrator - class: enhancer - reports_to: planner + reports_to: orchestrator notes: >- - Two-agent floor (orchestrator/planner + enhancer) is always staffed; every other - seat is added on demand. + Two-agent floor (orchestrator + enhancer) is always staffed; every other seat is + added on demand. diff --git a/packages/mosaic/framework/fleet/roles/LIBRARY.md b/packages/mosaic/framework/fleet/roles/LIBRARY.md index 28320f3..b605909 100644 --- a/packages/mosaic/framework/fleet/roles/LIBRARY.md +++ b/packages/mosaic/framework/fleet/roles/LIBRARY.md @@ -19,6 +19,7 @@ their intro so tooling can group them. | 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 | | 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 | diff --git a/packages/mosaic/framework/fleet/roles/orchestrator.md b/packages/mosaic/framework/fleet/roles/orchestrator.md new file mode 100644 index 0000000..92d73b9 --- /dev/null +++ b/packages/mosaic/framework/fleet/roles/orchestrator.md @@ -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). diff --git a/packages/mosaic/framework/fleet/roles/planner.md b/packages/mosaic/framework/fleet/roles/planner.md index 8882529..15fe120 100644 --- a/packages/mosaic/framework/fleet/roles/planner.md +++ b/packages/mosaic/framework/fleet/roles/planner.md @@ -3,11 +3,11 @@ The **planner** turns ratified objectives into an executable **plan** — phased functional requirements (FRs) wired into a `depends_on` DAG. -> **Alias:** the planner role IS the existing **orchestrator** class. The -> orchestrator _plays_ planner; this file documents the planning contract, it does -> **not** introduce a competing class. The two-agent floor (orchestrator + -> enhancer) is preserved — do not split planner into a separate persistent agent -> that would break it. +> **Reports to the orchestrator.** The planner is the goal-decomposition seat that +> the **orchestrator** dispatches planning work to; it carries the heavy +> goal-decomposition context, while the orchestrator holds only the lean +> coordination state. The two-agent floor is **orchestrator + enhancer** — the +> planner is added on demand, not part of the floor. It is a **front-office** role. @@ -19,8 +19,8 @@ It is a **front-office** role. between FRs so downstream decomposition can parallelize safely. 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. -4. **Re-plan on failure** — when execution diverges, the planner (orchestrator) - re-sequences the DAG rather than letting agents improvise. +4. **Re-plan on failure** — when execution diverges, the planner re-sequences the + DAG rather than letting agents improvise. ## Boundaries @@ -35,6 +35,7 @@ merge path. ## Persona 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). diff --git a/packages/mosaic/src/commands/fleet-profiles.spec.ts b/packages/mosaic/src/commands/fleet-profiles.spec.ts index 5e58884..f44322d 100644 --- a/packages/mosaic/src/commands/fleet-profiles.spec.ts +++ b/packages/mosaic/src/commands/fleet-profiles.spec.ts @@ -46,10 +46,12 @@ describe('listPersonaClasses (real role library)', () => { it('covers marker-less engineering personas via filename + LIBRARY index', async () => { const classes = await listPersonaClasses(rolesDir); - // planner/decomposition have a role file but no inline marker (planner aliases - // the orchestrator class) — they resolve from the filename + LIBRARY.md row. + // planner/decomposition have a role file but no inline marker — they resolve + // from the filename + LIBRARY.md row. expect(classes.has('planner')).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 () => { @@ -75,11 +77,17 @@ describe('baseline profiles (real library)', () => { it('software-delivery has the expected lead, floor, and roster shape', async () => { const profile = await loadProfile('software-delivery', realLib); - expect(profile.lead).toBe('planner'); - expect(profile.floor).toEqual(['planner', 'enhancer']); + expect(profile.lead).toBe('orchestrator'); + expect(profile.floor).toEqual(['orchestrator', 'enhancer']); const code = profile.roster.find((r) => r.class === 'code'); expect(code?.multiplicity).toBe(2); 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 () => {