Compare commits

..

1 Commits

Author SHA1 Message Date
Jarvis
92e4ee189a feat(fleet): update-surviving persona overrides (roles.local layer, resolver, persona CLI) (H4)
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
Add a PRESERVE-protected persona override layer at <mosaicHome>/fleet/roles.local/
that survives `mosaic update` while baseline fleet/roles/ keeps reseeding.

- fleet-personas.ts: shared class-extraction (single source of truth, DRY with
  fleet-profiles.ts), resolvePersona (override wins, baseline fallback),
  listPersonaClasses (baseline ⊕ override union), personaStatus
  (baseline/overridden/custom), and the `fleet persona list|show|customize` CLI.
- fleet-profiles.ts: roster validation now uses the override-aware union so a
  profile can reference a user-customized or user-ADDED persona; the old
  listPersonaClasses(rolesDir) is kept as a thin delegate to the shared helper.
- install.sh: add fleet/roles.local to PRESERVE_PATHS (AC-NS-7 guarantee).
- specs: override-wins, custom-add, status classification, AC-NS-7
  update-survival simulation, and profile-validation-accepts-custom-persona.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 11:11:00 -05:00
5 changed files with 31 additions and 91 deletions

View File

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

View File

@@ -19,7 +19,6 @@ 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

@@ -1,46 +0,0 @@
# 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.
> **Reports to the orchestrator.** The planner is the goal-decomposition seat that > **Alias:** the planner role IS the existing **orchestrator** class. The
> the **orchestrator** dispatches planning work to; it carries the heavy > orchestrator _plays_ planner; this file documents the planning contract, it does
> goal-decomposition context, while the orchestrator holds only the lean > **not** introduce a competing class. The two-agent floor (orchestrator +
> coordination state. The two-agent floor is **orchestrator + enhancer** — the > enhancer) is preserved — do not split planner into a separate persistent agent
> planner is added on demand, not part of the floor. > that would break it.
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 re-sequences the 4. **Re-plan on failure** — when execution diverges, the planner (orchestrator)
DAG rather than letting agents improvise. re-sequences the DAG rather than letting agents improvise.
## Boundaries ## Boundaries
@@ -35,7 +35,6 @@ 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 reports its plan back to the orchestrator that a clean DAG to decomposition, and keeps the orchestrator/enhancer floor intact.
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,12 +46,10 @@ 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 — they resolve // planner/decomposition have a role file but no inline marker (planner aliases
// from the filename + LIBRARY.md row. // the orchestrator class) — they resolve 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 () => {
@@ -77,17 +75,11 @@ 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('orchestrator'); expect(profile.lead).toBe('planner');
expect(profile.floor).toEqual(['orchestrator', 'enhancer']); expect(profile.floor).toEqual(['planner', '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 () => {