@yemi33/minions 0.1.2221 → 0.1.2222

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -136,7 +136,7 @@ const RENDER_VERSIONS = {
136
136
  inbox: 2,
137
137
  projects: 3,
138
138
  notes: 1,
139
- prd: 2,
139
+ prd: 3,
140
140
  prs: 3,
141
141
  archivedPrds: 1,
142
142
  engine: 4,
@@ -143,9 +143,19 @@ function renderPrd(prd, prog) {
143
143
  section.innerHTML = '';
144
144
  }
145
145
 
146
+ // Color a PR status for the PRD page pills/glyphs: merged -> green,
147
+ // abandoned/closed (any terminal-non-merged) -> red, active/other -> neutral
148
+ // default text color (no accent highlight).
149
+ function _prStatusColor(status) {
150
+ if (status === 'merged') return 'var(--green)';
151
+ if (!status || status === 'active') return 'var(--text)';
152
+ return 'var(--red)';
153
+ }
154
+
146
155
  function _renderPrLink(pr, opts) {
147
156
  var size = (opts && opts.size) || '10px';
148
157
  var statusIcon = pr.status === 'merged' ? '✓' : pr.status === 'abandoned' ? '✗' : '○';
158
+ var iconColor = _prStatusColor(pr.status);
149
159
  // P-79b47b0c — render as an in-stack modal chip via renderArtifactLink
150
160
  // (was a raw <a target="_blank">). The status icon stays as a non-link
151
161
  // prefix so the chip body remains the canonical id, matching the WI/PRD
@@ -154,7 +164,7 @@ function _renderPrLink(pr, opts) {
154
164
  ? renderArtifactLink({ type: 'pr', id: pr.id, label: pr.id, title: (pr.title || '') + ' (' + (pr.status || 'active') + ')' })
155
165
  : '<code>' + escHtml(pr.id) + '</code>';
156
166
  return '<span style="font-size:' + size + ';margin-left:4px;display:inline-flex;align-items:center;gap:3px">' +
157
- '<span title="' + escHtml(pr.status || 'active') + '">' + statusIcon + '</span>' + chip + '</span>';
167
+ '<span style="color:' + iconColor + '" title="' + escHtml(pr.status || 'active') + '">' + statusIcon + '</span>' + chip + '</span>';
158
168
  }
159
169
 
160
170
  function _renderPrdWorkItemIdBadge(workItemId, prdItemId, size, padding) {
@@ -621,7 +631,7 @@ function renderPrdProgress(prog) {
621
631
  if (prs.length > 0) {
622
632
  html += '<div style="font-size:var(--text-sm);font-weight:600;color:var(--blue);margin-bottom:4px">E2E Aggregate PRs</div>';
623
633
  html += prs.map(pr => {
624
- const statusColor = pr.status === 'active' ? 'var(--green)' : pr.status === 'merged' ? 'var(--purple)' : 'var(--muted)';
634
+ const statusColor = _prStatusColor(pr.status);
625
635
  // P-79b47b0c — render PR id as in-stack chip (was raw <a target="_blank">).
626
636
  const prChip = (typeof renderArtifactLink === 'function')
627
637
  ? renderArtifactLink({ type: 'pr', id: pr.id, label: pr.id, title: pr.title || pr.id })
@@ -70,7 +70,7 @@ async function openSettings() {
70
70
  return '<tr>' +
71
71
  '<td style="font-weight:600">' + escHtml(a.emoji || '') + ' ' + escHtml(a.name || id) + '</td>' +
72
72
  '<td><input data-agent="' + escHtml(id) + '" data-field="role" value="' + escHtml(a.role || '') + '" style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:var(--text-base)"></td>' +
73
- '<td><input data-agent="' + escHtml(id) + '" data-field="skills" value="' + escHtml((a.skills || []).join(', ')) + '" style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:var(--text-base)"></td>' +
73
+ '<td><input data-agent="' + escHtml(id) + '" data-field="expertise" value="' + escHtml((a.expertise || []).join(', ')) + '" style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:var(--text-base)"></td>' +
74
74
  '<td data-runtime-cli="' + escHtml(id) + '" style="min-width:110px">' +
75
75
  // Initial loading placeholder — initRuntimeFleetUI() replaces this with a
76
76
  // <select> populated from /api/runtimes once the registry resolves.
package/dashboard.js CHANGED
@@ -10684,7 +10684,15 @@ What would you like to discuss or change? When you're happy, say "approve" and I
10684
10684
  for (const [id, updates] of Object.entries(body.agents)) {
10685
10685
  if (!config.agents[id]) continue;
10686
10686
  if (updates.role !== undefined) config.agents[id].role = String(updates.role);
10687
- if (updates.skills !== undefined) config.agents[id].skills = Array.isArray(updates.skills) ? updates.skills : String(updates.skills).split(',').map(s => s.trim()).filter(Boolean);
10687
+ // Descriptive capability tags. Field renamed `skills` -> `expertise`
10688
+ // (issue: name collision with executable runtime skills). Accept the
10689
+ // legacy `skills` key from older clients and persist as `expertise`.
10690
+ const expertiseUpdate = updates.expertise !== undefined ? updates.expertise
10691
+ : (updates.skills !== undefined ? updates.skills : undefined);
10692
+ if (expertiseUpdate !== undefined) {
10693
+ config.agents[id].expertise = Array.isArray(expertiseUpdate) ? expertiseUpdate : String(expertiseUpdate).split(',').map(s => s.trim()).filter(Boolean);
10694
+ delete config.agents[id].skills;
10695
+ }
10688
10696
  if (updates.monthlyBudgetUsd !== undefined) {
10689
10697
  const val = updates.monthlyBudgetUsd === '' || updates.monthlyBudgetUsd === null ? undefined : Number(updates.monthlyBudgetUsd);
10690
10698
  if (val === undefined || isNaN(val)) delete config.agents[id].monthlyBudgetUsd;
package/docs/README.md CHANGED
@@ -15,7 +15,8 @@ Architecture, design proposals, and lifecycle references for people working on t
15
15
 
16
16
  - [branch-derivation.md](branch-derivation.md) — Engine-side branch fallback (`work/<wi-id>`) vs. agent-authored long form, the structured-vs-loose PR-pointer extractors, and the canonical PR-fix duplication incident.
17
17
  - [command-center.md](command-center.md) — Command Center (CC) chat panel: persistent Sonnet sessions, `--resume` semantics, system-prompt invalidation, and per-tab session storage.
18
- - [specs/agent-configurability.md](specs/agent-configurability.md) — Design spec for the user-configurable Role / Agent Library: Phase 1 exposes & edits each role's definition + skills; Phase 2 adds role/agent CRUD. Covers the additive `config.roles` model, resolver tiering, migration from per-agent `charter.md`, and API/UI surface.
18
+ - [specs/agent-rename.md](specs/agent-rename.md) — Design spec for **agent rename & name decoupling** the prerequisite for the Role/Agent Library. Formalizes the stable opaque agent id, makes charters name-agnostic, and adds `POST /api/agents/rename` (display name + emoji). Feature-flagged (`agent-rename`) and fully backward-compatible.
19
+ - [specs/agent-configurability.md](specs/agent-configurability.md) — Design spec for the user-configurable Role / Agent Library, feature-flagged behind `agent-library` and fully backward-compatible. Builds on `agent-rename.md`. Role = the reusable durable charter; agent = a thin instance (identity + variable expertise + role pointer). Phase 0 renames `skills`→`expertise`; Phase 1 exposes/edits role charters + per-agent expertise; Phase 2 adds role/agent CRUD with builtin delete-protection.
19
20
  - [completion-reports.md](completion-reports.md) — Canonical schema for the per-spawn completion JSON: trust nonce, `failure_class` enum, `noop` semantics, `retryable` / `needs_rerun` shape, and the artifacts array.
20
21
  - [constants.md](constants.md) — Cross-cutting status / type / condition constants (`WI_STATUS`, `WORK_TYPE`, `PR_STATUS`, `WATCH_CONDITION`, …) and the no-magic-strings invariant.
21
22
  - [constellation-bridge.md](constellation-bridge.md) — Read-only cross-repo bridge: `engine.constellationBridge.enabled` flag, marker-file contract, and the `minions bridge` subcommand for local debugging.
@@ -33,6 +34,7 @@ Architecture, design proposals, and lifecycle references for people working on t
33
34
  - [keep-processes.md](keep-processes.md) — `meta.keep_processes` sidecar contract: when to use it vs managed-spawn, sidecar schema, caps, and the [`engine/keep-process-sweep.js`](../engine/keep-process-sweep.js) lifecycle.
34
35
  - [live-checkout-mode.md](live-checkout-mode.md) — Per-project opt-in `checkoutMode: 'live'`: skips `git worktree add` and dispatches in-place inside `project.localPath` for `repo`-managed trees, submodule-heavy repos, deep Windows paths, and native build state. Includes the refuse-on-dirty contract and the per-project mutating-concurrency cap of 1.
35
36
  - [managed-spawn.md](managed-spawn.md) — Engine-owned long-running services (managed-spawn primitive): sidecar schema, healthcheck examples, lifecycle, dashboard API, and the WI 1 (build) → WI 2 (test) chained-validation pattern.
37
+ - [named-agents.md](named-agents.md) — The named-agent roster (Ripley / Dallas / Lambert / Rebecca / Ralph): per-agent `config.json` shape, the per-agent → `engine.*` → runtime resolution chain, how `routing.md` (not the `role`/`skills` metadata) drives dispatch, descriptive vs runtime skills, and per-agent memory files.
36
38
  - [plan-lifecycle.md](plan-lifecycle.md) — Full plan pipeline from `/plan` through PRD materialization, dispatch with dependency gating, verify task, and human archive.
37
39
  - [pr-auto-fix-dispatch.md](pr-auto-fix-dispatch.md) — Short reference table mapping each PR auto-fix / review dispatch site in `engine.js#discoverFromPrs` to its gate flag, plus the `pollingPaused` / `autoFixPaused` master kill-switches and the per-provider polling gates.
38
40
  - [pr-comment-followup.md](pr-comment-followup.md) — PR-comment follow-up dispatch contract: fix/review agents may spin off a new WI via `POST /api/work-items` with `meta.pr_followup` instead of broadening the current PR or rebutting the comment.
@@ -1,4 +1,17 @@
1
1
  [
2
+ {
3
+ "id": "agent-config-skills-field",
4
+ "description": "Legacy per-agent descriptive-metadata array `agents.<id>.skills` in config.json, renamed to `agents.<id>.expertise` to remove the name collision with executable runtime/harness skills (SKILL.md). The field is metadata only (capability tags like `architecture`, `bug-fixes`); nothing in the dispatch path reads it for behavior. A read-compat shim honors the old key so operator configs still carrying `skills` (and no `expertise`) keep working.",
5
+ "code": [
6
+ { "file": "engine/playbook.js", "lines": "950", "note": "buildSystemPrompt reads `agent.expertise ?? agent.skills ?? []` for the `Expertise:` identity line" },
7
+ { "file": "engine/lifecycle.js", "lines": "4620-4621", "note": "pickReReviewAgentHints reads `agent.expertise` with an `agent.skills` array fallback" },
8
+ { "file": "engine/queries.js", "lines": "731", "note": "getAgents normalizes `expertise: a.expertise ?? a.skills ?? []` so the dashboard/settings UI always receives `expertise`" },
9
+ { "file": "dashboard.js", "lines": "10708-10716", "note": "settings POST accepts a legacy `updates.skills` key, persists as `config.agents[id].expertise`, and deletes the old `skills` key" }
10
+ ],
11
+ "removalGate": "Telemetry / a config sweep across all known engines must show no persisted `config.agents.<id>.skills` key (only `expertise`) for >=30 consecutive days, confirming every operator config has been re-saved through the dashboard (which drops the legacy key) or hand-migrated.",
12
+ "targetRemovalDate": "2026-09-17",
13
+ "notes": "Safe to remove on or after 2026-09-17 (~3 release windows) once the removal gate clears. Removal scope: drop the `?? agent.skills` / `?? a.skills` fallbacks in engine/playbook.js:950, engine/lifecycle.js:4620-4621, and engine/queries.js:731; drop the `updates.skills` legacy branch + `delete config.agents[id].skills` in dashboard.js:10708-10716; and update docs/named-agents.md to stop mentioning the legacy key. Does NOT touch the unrelated executable-skills system (queries.js harnesses, runtime adapters, dashboard renderSkills, SKILL.md tooling)."
14
+ },
2
15
  {
3
16
  "id": "qa-json-sidecars",
4
17
  "location": "engine/small-state-store.js _mirrorQaRunsJson + _mirrorQaSessionsJson; engine/shared.js _qaMutator (mirror call); dashboard/js/settings.js set-qaDualWriteJson toggle",
@@ -0,0 +1,216 @@
1
+ # Named Agents — configuration, roles, and skills
2
+
3
+ Minions dispatches a small roster of **named agents** — personas like Ripley or Dallas
4
+ that own work items, open PRs, and review each other's changes. This doc explains who
5
+ they are, the full per-agent config shape, how work actually routes to one of them, what
6
+ the descriptive `expertise` tags and the real executable `skills` are (and why they used to
7
+ share a name), and how each agent accrues personal memory across dispatches.
8
+
9
+ > Orientation only — the load-bearing contracts live in code. Cross-references:
10
+ > [`CLAUDE.md`](../CLAUDE.md), [`.github/copilot-instructions.md`](../.github/copilot-instructions.md),
11
+ > [`docs/runtime-adapters.md`](runtime-adapters.md), [`docs/workspace-manifests.md`](workspace-manifests.md),
12
+ > [`docs/team-memory.md`](team-memory.md), and [`docs/skills.md`](skills.md).
13
+
14
+ ## 1. The roster
15
+
16
+ The five named agents are defined under `agents` in `config.json` (gitignored; the
17
+ shipped `config.template.json` carries no agents — operators populate the roster locally).
18
+ Each entry is keyed by a lowercase agent id (`ripley`, `dallas`, …).
19
+
20
+ | Id | Name | Emoji | Role | `expertise` (descriptive) | `cli` |
21
+ |----|------|-------|------|------------------------|-------|
22
+ | `ripley` | Ripley | 🏗️ | Lead / Explorer | `architecture`, `codebase-exploration`, `design-review` | `copilot` |
23
+ | `dallas` | Dallas | 🔧 | Engineer | `implementation`, `typescript`, `docker`, `testing` | `copilot` |
24
+ | `lambert` | Lambert | 📊 | Analyst | `gap-analysis`, `requirements`, `documentation` | `copilot` |
25
+ | `rebecca` | Rebecca | 🧠 | Architect | `system-design`, `api-design`, `scalability`, `implementation` | `copilot` |
26
+ | `ralph` | Ralph | ⚙️ | Engineer | `implementation`, `bug-fixes`, `testing`, `scaffolding` | `copilot` |
27
+
28
+ The `name`, `emoji`, `role`, and `expertise` fields are **human-facing metadata** — they
29
+ label the agent in the dashboard and document intent. They do **not** drive dispatch (see
30
+ §3) and the `expertise` array is not executable (see §4).
31
+
32
+ ## 2. How an agent is configured
33
+
34
+ The full per-agent config shape supported under `config.agents.<id>`:
35
+
36
+ | Field | Type | Meaning |
37
+ |-------|------|---------|
38
+ | `name` | string | Display name (e.g. `"Ripley"`). |
39
+ | `role` | string | Human-facing role label (e.g. `"Lead / Explorer"`). Metadata only. |
40
+ | `emoji` | string | Dashboard glyph. |
41
+ | `expertise` | string[] | Descriptive capability tags. Metadata only — see §4. Legacy configs that still use the old key name `skills` are read transparently. |
42
+ | `cli` | string | Runtime CLI for this agent's spawns: `copilot`, `claude`, or `codex`. |
43
+ | `model` | string | Per-agent model override (omitted → runtime picks its own default). |
44
+ | `maxBudgetUsd` | number | Per-spawn USD cap. **`0` is a valid cap** (read-only / dry-run agents), not "unlimited". |
45
+ | `bareMode` | boolean | Run Claude in `--bare` mode (Claude-runtime knob). |
46
+ | `hermeticHarness` | boolean | Strip user-scope skill/command/MCP roots from the worktree; `--add-dir` resolves to `[minionsDir]` only. |
47
+ | `workspace_manifest` | object | Optional permission scoping — see below. |
48
+
49
+ ### Three-tier resolution chain
50
+
51
+ Most knobs resolve **per-agent override → `engine.*` fleet default → runtime default**.
52
+ The resolver helpers live in [`engine/shared.js`](../engine/shared.js):
53
+
54
+ | Helper | Chain |
55
+ |--------|-------|
56
+ | `resolveAgentCli(agent, engine)` | `agent.cli` → `engine.defaultCli` → `ENGINE_DEFAULTS.defaultCli` (`'copilot'`). |
57
+ | `resolveAgentModel(agent, engine)` | `agent.model` → `engine.defaultModel` → `undefined` (adapter omits `--model`, CLI uses its own default). |
58
+ | `resolveAgentMaxBudget(agent, engine)` | `agent.maxBudgetUsd` → `engine.maxBudgetUsd` → `undefined` (no cap). Nullish-coalesced so `0` is honored. |
59
+ | `resolveAgentBareMode(agent, engine)` | `agent.bareMode` → `engine.claudeBareMode` → `false`. Strict null check so a per-agent `false` overrides an engine `true`. |
60
+ | `resolveAgentHermeticHarness(agent, engine)` | `agent.hermeticHarness` → `engine.hermeticHarness` → `false`. Same strict-override semantics. |
61
+
62
+ Empty string / `null` / `undefined` all count as "unset" for the string knobs, so an agent
63
+ inherits the fleet default rather than blanking the value.
64
+
65
+ ### Agent vs Command Center independence
66
+
67
+ The Command Center (CC) / doc-chat path is a fleet-wide singleton with **no notion of
68
+ "which agent"**, so it resolves through separate helpers: `resolveCcCli(engine)`
69
+ (`engine.ccCli` → `engine.defaultCli` → fallback) and `resolveCcModel(engine)`
70
+ (`engine.ccModel` → `engine.defaultModel` → `undefined`). These **do not cross over**:
71
+ `ccCli` / `ccModel` never affect a named-agent spawn, and `agent.cli` / `agent.model` never
72
+ affect CC. Both paths share the `engine.defaultCli` / `engine.defaultModel` fleet defaults,
73
+ but only as the middle tier.
74
+
75
+ ### Workspace manifest (optional)
76
+
77
+ `agents.<id>.workspace_manifest` declares declarative permission scoping. Shape and
78
+ defaults (`WORKSPACE_MANIFEST_DEFAULTS` in `engine/shared.js`):
79
+
80
+ | Key | Default | Effect |
81
+ |-----|---------|--------|
82
+ | `allowed_tools` | `null` (permissive) | Whitelist of tool names; merged into the runtime `--allowedTools` flag. `[]` = deny-all. |
83
+ | `allowed_repos` | `null` (permissive) | Repo allow-list; enforced at dispatch time. `[]` = deny-all. |
84
+ | `allowed_external_urls` | `null` (permissive) | URL allow-list (supports `*.example.com` wildcards). |
85
+ | `memory_scope` | `'shared'` | One of `private`, `shared`, `read-only-shared`. |
86
+
87
+ Enforcement helpers (also in `engine/shared.js`): `validateWorkspaceManifest`,
88
+ `resolveAgentManifest`, `agentCanUseRepo`, `agentCanUseTool`, `agentCanFetchUrl`,
89
+ `agentMemoryScope`, `mergeManifestAllowedTools`. Today the engine actively enforces (a) the
90
+ **repo gate** at dispatch time in `engine.js spawnAgent`
91
+ (`FAILURE_CLASS.WORKSPACE_MANIFEST_REPO`, non-retryable) and (b) the **tool merge** into the
92
+ runtime `--allowedTools` flag at spawn time. Defaults are permissive — an agent with no
93
+ manifest is unaffected. Full schema and rollout guidance:
94
+ [`docs/workspace-manifests.md`](workspace-manifests.md).
95
+
96
+ ## 3. How roles are defined / how work routes to an agent
97
+
98
+ The `role` and `expertise` config fields are **descriptive metadata**. The actual dispatch
99
+ decision is **data-driven by [`routing.md`](../routing.md)**, which `engine.js` parses — so
100
+ keep that file's table format exact.
101
+
102
+ Routing maps a `work_type` to a `preferred` agent and a `fallback`:
103
+
104
+ | Work Type | Preferred | Fallback |
105
+ |-----------|-----------|----------|
106
+ | implement | dallas | ralph |
107
+ | implement:large | rebecca | dallas |
108
+ | review | ripley | lambert |
109
+ | fix | `_author_` | `_any_` |
110
+ | plan | ripley | rebecca |
111
+ | plan-to-prd | lambert | rebecca |
112
+ | explore | ripley | rebecca |
113
+ | test | dallas | ralph |
114
+ | ask | ripley | rebecca |
115
+ | verify | dallas | ralph |
116
+ | decompose | ripley | rebecca |
117
+ | meeting | ripley | lambert |
118
+ | docs | lambert | `_any_` |
119
+ | setup | dallas | `_any_` |
120
+ | qa-validate / qa-session-* | dallas | ralph |
121
+
122
+ **Sentinels.** `_author_` routes a `fix` (review-feedback) item back to the PR author for
123
+ context; `_any_` routes to any available idle agent (lowest error rate first).
124
+ `implement:large` is selected for items with `estimated_complexity: "large"`. If both
125
+ preferred and fallback are busy, the engine falls back to any idle agent.
126
+
127
+ **Dispatch rules** (from `routing.md`):
128
+
129
+ - **Eager by default** — the engine spawns every agent that can start work, not one at a time.
130
+ - **No self-review** — auto review dispatch always picks a non-author. When no non-author
131
+ reviewer is idle, the dispatch waits in `dispatch.pending` with
132
+ `_pendingReason: 'no_non_author_reviewer'` and promotes automatically once one frees up.
133
+ An explicit operator dispatch (`agent: <author>`) is honored as an override but logged as
134
+ a warning.
135
+ - **Routing selects an owner; it does not narrow the task.** The assigned agent behaves as
136
+ if the user typed the same task into a CLI directly — Minions adds only safety, status,
137
+ and review guardrails.
138
+
139
+ ### Per-agent retry reassignment
140
+
141
+ Total retries are capped by `ENGINE_DEFAULTS.maxRetries` (default 3). Separately, each work
142
+ item tracks per-agent attempts in `_retriesByAgent: { agentId: count }`. When the **same**
143
+ agent fails the **same** item `maxRetriesPerAgent` times (default 2; overridable via
144
+ `engine.maxRetriesPerAgent` and the dashboard Settings field), discovery force-reassigns to
145
+ a different eligible agent via `resolveAgent(workType, config, { excludeAgent })`. If no
146
+ alternate is available it stays with the same agent and dedups an engine inbox note. Setting
147
+ `agentLock: true` (or `hardAgent: true`) hard-pins the agent and bypasses reassignment —
148
+ operator intent wins. The counter is cleared on successful completion alongside
149
+ `_retryCount`. Helpers: `bumpAgentRetryCount`, `getAgentRetryCount`,
150
+ `resolveMaxRetriesPerAgent` in `engine/shared.js`.
151
+
152
+ ## 4. How skills are defined
153
+
154
+ Historically "skills" meant two unrelated things in Minions. The descriptive per-agent
155
+ metadata array was renamed `skills` → `expertise` to remove that collision, so the only
156
+ "skills" left are the real executable ones. The two concepts to keep separate:
157
+
158
+ **(a) The descriptive `expertise` array in config** (`agents.<id>.expertise`). A list of
159
+ capability tags like `architecture` or `bug-fixes`. This is **metadata only** — labels for
160
+ humans, not executable behavior. Nothing in the dispatch path reads it. (This field was
161
+ formerly named `skills`; legacy configs that still use that key are read transparently via
162
+ an `expertise ?? skills` fallback — see [`docs/deprecated.json`](deprecated.json).)
163
+
164
+ **(b) Real runtime skills** — `SKILL.md` files the runtime CLI actually loads. These are
165
+ auto-extracted from agent output: when an agent emits a fenced ```` ```skill ```` block
166
+ (with YAML frontmatter carrying at least a `name`), `extractSkillsFromOutput` in
167
+ [`engine/lifecycle.js`](../engine/lifecycle.js) writes it out. The engine also scans
168
+ `notes/inbox/` notes for skill blocks, since agents often write them there instead of stdout.
169
+
170
+ - **`scope: minions`** (the default when `scope` is omitted) → written to the runtime's
171
+ **personal** skill directory as `<name>/SKILL.md`, so it becomes a user-level skill
172
+ available in normal runtime windows too.
173
+ - **`scope: project`** (with a `project:` field) → the engine queues a low-priority
174
+ `implement` work item to **PR the skill into the project repo's** skill directory rather
175
+ than writing it directly.
176
+
177
+ Near-duplicate names are flagged to `engine/skills-flagged.json` and skipped (no existing
178
+ skill is deleted or renamed). See [`docs/skills.md`](skills.md) for the block format.
179
+
180
+ **Runtime differences.** The destination directory is runtime-specific
181
+ (`getSkillRoots` / `getSkillWriteTargets` in the runtime adapters under
182
+ [`engine/runtimes/`](../engine/runtimes/)):
183
+
184
+ | Runtime | Personal write target | Project write target | Also reads |
185
+ |---------|-----------------------|----------------------|------------|
186
+ | Claude (`claude.js`) | `~/.claude/skills/` | `<repo>/.claude/skills/` | `~/.agents/skills/` |
187
+ | Copilot (`copilot.js`) | `~/.copilot/skills/` | `<repo>/.github/skills/` | `~/.copilot`, `~/.agents/skills/` |
188
+
189
+ The capability matrix for each runtime is in [`docs/runtime-adapters.md`](runtime-adapters.md).
190
+
191
+ ## 5. Per-agent memory & charters
192
+
193
+ Each named agent has a personal memory file at
194
+ `knowledge/agents/<agentId>.md` (e.g. `knowledge/agents/dallas.md`) — a personal notebook
195
+ injected into that agent's prompt on every dispatch. The format convention is documented in
196
+ [`knowledge/agents/README.md`](../knowledge/agents/README.md).
197
+
198
+ **Written exclusively by the consolidation pipeline.** `appendToAgentMemory` in
199
+ [`engine/consolidation.js`](../engine/consolidation.js) routes each `notes/inbox/` finding
200
+ to its author's file. Authorship comes from the inbox note's YAML frontmatter `agent:`
201
+ field, with the **filename prefix** (`<agent>-…md`) as fallback (`extractInboxAgent`). Key
202
+ properties:
203
+
204
+ - **Append-only** — agents must not edit their own memory files directly; write to
205
+ `notes/inbox/` and let the sweep route it.
206
+ - **~25 KB cap** (`AGENT_MEMORY_BUDGET_BYTES`) — oldest sections pruned at section
207
+ boundaries to stay under budget.
208
+ - **Only configured `config.agents` keys get a file** — the agent must be in the known-team
209
+ set; `temp-*` ids are skipped. This is a strict superset of broadcast consolidation: the
210
+ shared `notes.md` digest still happens; per-agent routing is *in addition*.
211
+
212
+ **Prompt injection order.** [`engine/playbook.js`](../engine/playbook.js) injects, in this
213
+ order, `pinned.md` → `notes.md` → `knowledge/agents/<agentId>.md` (when present). Missing
214
+ files are silently skipped. The per-agent file is fenced as `<UNTRUSTED-INPUT>` and capped
215
+ to the notes prompt budget before injection. The companion read-side reference is
216
+ [`docs/team-memory.md`](team-memory.md).
@@ -3,32 +3,40 @@
3
3
  **Status:** Draft
4
4
  **Owner:** @calebt
5
5
  **Audience:** Minions contributors working on the dashboard, engine config surface, and playbook/system-prompt rendering.
6
+ **Depends on:** [`agent-rename.md`](agent-rename.md) — a **hard prerequisite**. That spec delivers the "names aren't load-bearing" foundation (stable opaque agent **id**, name-agnostic charters, and the agent rename capability) that this Library builds on. The decoupling sections below (§2.8, §4.1a, §4.3c, §5.4) are **summaries that defer to `agent-rename.md`** for the full treatment.
6
7
 
7
- > Companion reading: [`.github/copilot-instructions.md`](../../.github/copilot-instructions.md) and [`CLAUDE.md`](../../CLAUDE.md) for the engine/dashboard split and config conventions.
8
+ > Companion reading: [`.github/copilot-instructions.md`](../../.github/copilot-instructions.md) and [`CLAUDE.md`](../../CLAUDE.md) for the engine/dashboard split, config conventions, and the feature-flag registry.
8
9
 
9
10
  ## 1. Motivation
10
11
 
11
- Today a Minions "team" is a fixed roster of five hard-coded agents (`ripley`, `dallas`, `lambert`, `rebecca`, `ralph`) whose personas live in a mix of places: a free-text `role` string in `config.json`, a loosely-related `skills` string array, and a per-agent `charter.md` file. There is **no way to**:
12
+ Today a Minions "team" is a fixed roster of five hard-coded agents (`ripley`, `dallas`, `lambert`, `rebecca`, `ralph`) whose personas live in a mix of places: a free-text `role` string in `config.json`, a loosely-related `skills` string array, and a per-agent `charter.md` file. The charter — the real persona — is **per-agent**, so there is no reusable notion of "what it means to be an Engineer." There is **no way to**:
12
13
 
13
- - See an agent's full definition (persona + skills + runtime) in one place.
14
- - Treat a "role" as a reusable, editable unit — it is just a label string today.
14
+ - See an agent's full definition (role charter + expertise + runtime) in one place.
15
+ - Treat a "role" as a reusable, editable unit (a durable charter shared across agents) — it is just a label string today.
16
+ - **Rename** an agent's display name.
15
17
  - Add a brand-new role, or stand up a new named agent, from the dashboard.
16
18
 
17
- The goal is to make the fleet **user-configurable** through a **Role / Agent Library**, delivered in two phases:
19
+ The goal is to make the fleet **user-configurable** through a **Role / Agent Library** built on one core idea: **the role owns the durable charter ("what it means to be an X") and is the reusable unit; an agent is a thin named instance that points at a role and carries only its identity, its variable expertise, and runtime knobs** (§3). Delivered in phases, with **two cross-cutting constraints on every phase:**
18
20
 
19
- - **Phase 1 Expose & edit.** Surface the skill + definition for each existing role/agent in one editable library view. No new roles or agents yet; just make what exists first-class and editable.
21
+ - **Backwards compatible.** Existing `config.json` files, per-agent `charter.md` files, `routing.md`, and all agent-keyed state must keep working with zero migration required from the user. New config shapes are additive; back-compat readers are unconditional.
22
+ - **Feature-flagged.** All new surfaces in *this* spec (the Library UI, the role model, CRUD) sit behind a single registry flag `agent-library`, default **OFF**, so the change ships dark and is enabled per-install (see §3.1). The agent **rename** capability is gated separately by its own `agent-rename` flag in the [`agent-rename.md`](agent-rename.md) prerequisite.
23
+
24
+ Phases:
25
+
26
+ - **Phase 0 — Prerequisite migrations.** Establish the feature flag and rename the misnamed `skills` field to `expertise`. (The stable-id formalization and name-agnostic charters are delivered by the **[`agent-rename.md`](agent-rename.md) prerequisite**, which must land first.) All additive + flag-aware.
27
+ - **Phase 1 — Expose & edit.** Surface each role's durable charter + each agent's variable expertise in one editable library view; the agent **rename** capability comes from the prerequisite and is surfaced inline here. No new roles or agents yet.
20
28
  - **Phase 2 — Create & assign.** Add new roles, and stand up new named agents bound to a role.
21
29
 
22
30
  ## 2. Current state (grounding)
23
31
 
24
32
  This section is the factual baseline the design must respect. Citations are `file:line` against the repo at spec-authoring time.
25
33
 
26
- ### 2.1 Agent object
34
+ ### 2.1 Agent object — already id-keyed
27
35
 
28
- Agents live under `config.agents` keyed by lowercase id. Defaults are hard-coded:
36
+ Agents live under `config.agents` keyed by a **stable lowercase id** (the object key). `name` is a **separate display field** — there is no explicit `id` field inside the object; the key *is* the id, and `getAgents()` materializes it as `{ id, ...info }` (`engine/queries.js:681-687`). Defaults are hard-coded:
29
37
 
30
38
  ```js
31
- // engine/shared.js:4350-4356
39
+ // engine/shared.js:4398-4404
32
40
  const DEFAULT_AGENTS = {
33
41
  ripley: { name: 'Ripley', emoji: '🏗️', role: 'Lead / Explorer', skills: ['architecture', 'codebase-exploration', 'design-review'] },
34
42
  dallas: { name: 'Dallas', emoji: '🔧', role: 'Engineer', skills: ['implementation', 'typescript', 'docker', 'testing'] },
@@ -38,6 +46,8 @@ const DEFAULT_AGENTS = {
38
46
  };
39
47
  ```
40
48
 
49
+ Crucially, **nothing derives the id from the name** — `id === lowercase(name)` is coincidental, not enforced. Routing only lowercases routing-table cells, not names (`engine/routing.js:31-52`); agent lookup is by key with case-insensitive normalization against existing keys (`engine/routing.js:160-189`). So `name !== lowercase(id)` is already safe. This is the foundation that makes **rename = change `name`, keep `id`** trivial (see §2.8; full treatment in [`agent-rename.md`](agent-rename.md)).
50
+
41
51
  Runtime / budget fields are **optional per-agent overrides** resolved against `engine.*` fleet defaults via helpers in `engine/shared.js`:
42
52
 
43
53
  | Field | Resolver | Falls back to |
@@ -47,26 +57,20 @@ Runtime / budget fields are **optional per-agent overrides** resolved against `e
47
57
  | `maxBudgetUsd` / `monthlyBudgetUsd` | `resolveAgentMaxBudget` (shared.js:3231) | `engine.maxBudgetUsd` |
48
58
  | `bareMode` | `resolveAgentBareMode` (shared.js:3254) | `engine.claudeBareMode` → false |
49
59
 
50
- So the effective agent shape is:
51
-
52
- ```jsonc
53
- { "name": "Dallas", "emoji": "🔧", "role": "Engineer",
54
- "skills": ["implementation","testing"],
55
- "cli": "claude", "model": "haiku", "monthlyBudgetUsd": 5, "bareMode": false }
56
- ```
57
-
58
60
  ### 2.2 "Role" is a label, not an entity
59
61
 
60
62
  `role` is a **free-text string** on the agent. There is no role registry, no role schema, and routing does **not** reference roles — it references agent ids. The dashboard edits role as a plain text input (`dashboard/js/settings.js:72`) and persists it verbatim (`dashboard.js:10684`).
61
63
 
62
- ### 2.3 `skills` is metadata, not real skills
64
+ ### 2.3 `skills` is *expertise*, not real skills
63
65
 
64
- Two unrelated things share the word "skill":
66
+ Two unrelated things share the word "skill" today, and the `agent.skills` field is the **misnamed** one:
65
67
 
66
- 1. **`agent.skills`** — a string array, surfaced only as an `Expertise: …` line in the system prompt (`engine/playbook.js:950`). It is **not** linked to any executable capability.
67
- 2. **Real `SKILL.md` files** discovered from disk (`.claude/skills/`, `~/.claude/skills`, project harness dirs) by `collectSkillFiles()` / `getSkills()` (`engine/queries.js:1263-1400`), scoped global/project/plugin. These are **not** bound to a specific agent or role; every agent sees the same discovered catalog.
68
+ 1. **`agent.skills`** — a string array of **expertise tags**. It is *not* linked to any executable capability or on-disk skill file. It has exactly two readers:
69
+ - **Display:** rendered as the `Expertise: …` line in the system prompt (`engine/playbook.js:950`).
70
+ - **Soft routing:** `pickReReviewAgentHints()` (`engine/lifecycle.js:4613-4624`) scans `agent.skills` for the tags `code-review` / `design-review` to *prefer* (not require) that agent for re-review. This is the one functional consumer — a rename must update it too, not just the display line.
71
+ 2. **Real `SKILL.md` files** — discovered from disk (`.claude/skills/`, `~/.claude/skills`, project harness dirs) by `collectSkillFiles()` / `getSkills()` (`engine/queries.js:1263-1400`), scoped global/project/plugin. These are **not** bound to a specific agent or role; every agent sees the same discovered catalog, and `agent.skills` has **no relationship** to them.
68
72
 
69
- This split is a key source of the "agents aren't really configurable" feeling and must be addressed explicitly (see §4.3).
73
+ Because `agent.skills` is purely expertise metadata (plus a soft reviewer hint), the field is misnamed. **Phase 0b renames it `expertise`** to remove the overload and free the word "skills" for any future real-skill binding 8).
70
74
 
71
75
  ### 2.4 Charter = the real "agent definition"
72
76
 
@@ -75,7 +79,7 @@ The substantive persona/instructions for an agent is `agents/<id>/charter.md`, r
75
79
  ```
76
80
  # You are {name} ({role})
77
81
  Agent ID: {id}
78
- Expertise: {skills joined}
82
+ Expertise: {expertise joined}
79
83
 
80
84
  ## Your Charter
81
85
  {charter.md contents}
@@ -84,6 +88,8 @@ Expertise: {skills joined}
84
88
 
85
89
  Charter is already editable today, but only from the **agent detail panel → Charter tab** (`dashboard/js/detail-panel.js:115-126`) via `POST /api/agents/charter` (`dashboard.js:13205-13211`). It is disconnected from the Settings → Agents table.
86
90
 
91
+ **Today's default charters hardcode names.** The shipped charters bake the agent's own name and peers' names directly into the prose — e.g. `agents/ripley/charter.md` opens `# Ripley — Lead / Explorer`, `**Name:** Ripley`, and says "Handing off structured findings to **Dallas** (Engineer) and **Lambert** (Analyst)". This is fine today (names are fixed) but **collides with two new capabilities**: renaming an agent (Phase 1) would leave the old name strewn through the prompt, and a role-level `charter` shared across multiple agents can't bake in any one agent's name. Role charters must become **name-agnostic** (§4.3c).
92
+
87
93
  ### 2.5 Routing
88
94
 
89
95
  `routing.md` is a markdown table mapping work-type → preferred/fallback **agent id** (`routing.md:8-27`), parsed by `engine/routing.js:31-52`. `resolveAgent()` (`engine/routing.js:192`) resolves the route, honoring `_author_` / `_any_` tokens, idleness, budget, and the self-review ban. Routing is edited via `POST /api/settings/routing`, which overwrites `routing.md` wholesale (`dashboard.js:10799-10805`).
@@ -91,47 +97,71 @@ Charter is already editable today, but only from the **agent detail panel → Ch
91
97
  ### 2.6 Config read / write surface
92
98
 
93
99
  - **Read:** `queries.getConfig()` (`engine/queries.js:181-225`); Settings read endpoint returns `{ engine, claude, agents, projects, routing }` (`dashboard.js:10362-10391`).
94
- - **Write:** `POST /api/settings` edits `engine`, `claude`, **existing** `agents[id]` (role, skills, cli, model, budget — `dashboard.js:10682-10705`), and projects, then saves `config.json`. **There is no agent create/delete path** — the loop `for (const [id, updates] of Object.entries(body.agents))` skips unknown ids (`dashboard.js:10683`).
100
+ - **Write:** `POST /api/settings` edits `engine`, `claude`, **existing** `agents[id]` (role, skills, cli, model, budget — `dashboard.js:10682-10705`; **note: `name` is NOT editable today**), and projects, then saves `config.json`. **There is no agent create/delete/rename path** — the loop `for (const [id, updates] of Object.entries(body.agents))` skips unknown ids (`dashboard.js:10683`).
95
101
  - **Reset:** restores `engine` defaults + `shared.DEFAULT_AGENTS` (`dashboard.js:10808-10815`).
96
- - **UI:** Settings → Runtime pane has an **edit-only** Agents table (Role, Skills, CLI, Model, Budget) — `dashboard/js/settings.js:152-157`. No add/remove.
102
+ - **UI:** Settings → Runtime pane has an **edit-only** Agents table (Role, Skills, CLI, Model, Budget) — `dashboard/js/settings.js:152-157`. No add/remove/rename.
97
103
 
98
104
  ### 2.7 Gaps summary
99
105
 
100
106
  | Capability | Today |
101
107
  |------------|-------|
102
108
  | View an agent's full definition in one place | ✗ (split across settings table + charter tab) |
103
- | Role as a reusable, editable unit | ✗ (free-text label) |
104
- | Edit charter from the library/settings | ✗ (only detail panel) |
105
- | Bind skills to a role/agent meaningfully | ✗ (metadata only) |
109
+ | Role as a reusable, editable unit (durable charter) | ✗ (free-text label; charter is per-agent) |
110
+ | Reuse one charter across many agents | ✗ (each agent has its own `charter.md`) |
111
+ | Edit a role's charter from the library/settings | ✗ (only the per-agent detail panel) |
112
+ | Rename an agent's display name (+ emoji) | ✗ |
113
+ | `expertise` field named for what it is | ✗ (called `skills`, overloaded) |
106
114
  | Add a new role | ✗ |
107
115
  | Add a new named agent | ✗ |
108
116
  | Delete an agent | ✗ |
109
117
 
118
+ ### 2.8 Agent identity is a stable opaque id
119
+
120
+ > **Summary — full treatment in [`agent-rename.md`](agent-rename.md) §2.**
121
+
122
+ Agents are **already id-keyed**: every piece of agent-scoped state (charter file, per-agent memory, metrics, dispatch/PR records, inbox prefixes, routing) is keyed by the immutable `config.agents` **key**, never the display `name`. The only consumers of `name` are `buildSystemPrompt`'s `# You are {name}` line and dashboard display text. So the id is the stable handle the role model relies on (an agent can be renamed or reassigned to a different role without touching any id-keyed state), and it must never be mutated. The rename prerequisite formalizes this invariant with a guard + tests.
123
+
110
124
  ## 3. Target model: the Role / Agent Library
111
125
 
112
- Introduce a **Role** as a first-class, reusable definition, and make **Agents** named instances that reference a role and optionally override pieces of it.
126
+ **The role is the reusable unit, and the role *is* the durable charter.** An agent's definition splits into two parts along a **durable vs. variable** seam:
127
+
128
+ - **Durable (role-level, reusable):** the **charter** — "what it means to be an Engineer": responsibilities, working style, ownership, guardrails. Authored once on the role and shared, unchanged, by every agent of that role. Long-form, name-agnostic (§3.3, §4.3c).
129
+ - **Variable (agent-level, per-instance):** the **expertise** — the specialization that distinguishes two agents of the *same* role (e.g. one Engineer focused on TypeScript/Docker, another on bug-fixes/scaffolding). Short-form, part of the agent's identity (§3.3).
130
+
131
+ So an **agent is a thin named instance**: identity (name, emoji) + **its variable expertise** + a pointer to a role (which supplies the durable charter) + runtime knobs. There is **no** per-agent charter override — if you need a different *persona*, you pick/make a different role; if you only need a different *specialization*, that's expertise.
113
132
 
114
133
  ```jsonc
115
- // config.json (additive — see §5 migration)
134
+ // config.json (additive — see §6 migration)
116
135
  {
136
+ "features": { "agent-library": true }, // gates all new surfaces (§3.1)
137
+
138
+ // ROLE = the reusable, DURABLE definition. The charter ("what it means to
139
+ // be an X") + label + runtime defaults live here. Shared across agents.
117
140
  "roles": {
118
141
  "engineer": {
119
- "label": "Engineer", // human-facing name
120
- "definition": "<markdown persona>", // the role-level charter (system-prompt body)
121
- "skills": ["implementation", "testing", "bug-fixes"],
122
- "defaultCli": null, // optional role-level runtime defaults
142
+ "label": "Engineer", // human-facing role name
143
+ "charter": "<markdown persona>", // THE durable definition — long-form, name-agnostic (§3.3, §4.3c)
144
+ "defaultCli": null, // optional role-level runtime defaults
123
145
  "defaultModel": null,
124
- "builtin": true // seeded from DEFAULT_ROLES; protects from delete in P1
146
+ "builtin": true // seeded from DEFAULT_ROLES editable but NEVER deletable (§3.2)
125
147
  },
126
- "architect": { "label": "Architect", "definition": "…", "skills": ["system-design","api-design"] }
148
+ "architect": { "label": "Architect", "charter": "…" }
127
149
  },
150
+
151
+ // AGENT = a thin named instance: identity + VARIABLE expertise + role pointer
152
+ // + runtime overrides. No charter here — the durable charter comes from the role.
128
153
  "agents": {
129
- "dallas": {
130
- "name": "Dallas", "emoji": "🔧",
131
- "role": "engineer", // now references roles{} key (was free text)
132
- "skills": [], // agent-level ADDITIONS to the role's skills
133
- "definitionOverride": null, // optional per-agent charter override
134
- "cli": null, "model": null, "monthlyBudgetUsd": null, "bareMode": false
154
+ "dallas": { // ← the KEY is the stable opaque id; never mutated
155
+ "name": "Dallas", // ← identity: display name; freely renamable (Phase 1)
156
+ "emoji": "🔧", // identity: emoji
157
+ "role": "engineer", // supplies the durable charter + label
158
+ "expertise": ["typescript", "docker", "testing"], // VARIABLE: this instance's specialization
159
+ "cli": null, "model": null, "monthlyBudgetUsd": null, "bareMode": false // runtime knobs only
160
+ },
161
+ "ralph": {
162
+ "name": "Ralph", "emoji": "⚙️",
163
+ "role": "engineer", // ← same durable charter as Dallas…
164
+ "expertise": ["bug-fixes", "scaffolding", "testing"] // ← …but different variable expertise
135
165
  }
136
166
  }
137
167
  }
@@ -139,133 +169,244 @@ Introduce a **Role** as a first-class, reusable definition, and make **Agents**
139
169
 
140
170
  **Resolution rules (engine):**
141
171
 
142
- - Effective **definition** = `agent.definitionOverride` ?? `roles[agent.role].definition` ?? legacy `agents/<id>/charter.md`. (Keeps existing charters working — see §5.)
143
- - Effective **skills** = union of `roles[agent.role].skills` and `agent.skills`.
144
- - Effective **label** shown in `# You are {name} ({label})` = `roles[agent.role].label` ?? raw `agent.role` string (legacy).
145
- - Effective **cli/model** chain unchanged, with an inserted role tier: `agent.cli` → `roles[agent.role].defaultCli` → `engine.defaultCli` → `'copilot'`.
172
+ - Effective **charter (durable)** = `roles[agent.role].charter` ?? legacy `agents/<id>/charter.md` (the legacy per-agent file is read only as a back-compat fallback for agents whose role has no charter yet — see §6). Name-agnostic: the agent's name is supplied *only* by the injected `# You are {name} ({label})` identity line, never baked into the charter body (§4.3c).
173
+ - Effective **expertise (variable)** = `agent.expertise` (with `agent.skills` read as a back-compat alias — see §4.2b).
174
+ - Effective **label** shown in `# You are {name} ({label})` = `roles[agent.role].label` ?? raw `agent.role` string (legacy free-text role).
175
+ - Effective **cli/model** chain gains a role tier: `agent.cli` → `roles[agent.role].defaultCli` → `engine.defaultCli` → `'copilot'`.
176
+
177
+ The **durable** persona resolves entirely from the role; the **variable** expertise and the **identity** (`name`, `emoji`, `id`) are per-agent. These are the two **dimensions** of an agent — see §3.3. This preserves every existing resolver's external behavior when `roles` is absent or `agent.role` is still free-text (legacy), and layers the library in additively.
178
+
179
+ ### 3.1 Feature flag (cross-cutting)
180
+
181
+ A single registry flag gates everything user-visible in this spec:
182
+
183
+ - Register `agent-library` in the `FEATURES` registry (`engine/features.js`) as `{ description, default: false, addedIn, expires }`.
184
+ - **Engine** gates new behavior with `features.isFeatureOn('agent-library', config)`; **dashboard JS** gates new UI with `MinionsFeatures.isOn('agent-library')`.
185
+ - Toggle via Settings → Experimental flags, `config.features['agent-library']: true`, or `MINIONS_FEATURE_AGENT_LIBRARY=1`. Resolution order: env → `config.features` → registry default (default OFF).
186
+ - **What the flag gates:** the Library page/API (`/api/roles*`), agent rename UI, role/agent CRUD UI, and the role-tier resolution layer.
187
+ - **What the flag does NOT gate (always on):** the unconditional back-compat *readers* — `agent.expertise ?? agent.skills`, `roles[agent.role].charter ?? agents/<id>/charter.md`, and `roles[agent.role].label ?? agent.role`. These are pure fallbacks that are inert on a legacy config and must work whether or not the flag is set, so an install that flips the flag off after editing never breaks. When the flag is OFF and no `roles` exist, behavior is byte-for-byte identical to today.
188
+
189
+ When the feature graduates, delete the registry entry + gates (per CLAUDE.md feature-flag lifecycle).
190
+
191
+ ### 3.2 Builtin (pre-installed) roles & agents are protected
192
+
193
+ The roles and agents seeded from `DEFAULT_ROLES` / `DEFAULT_AGENTS` are **builtin** and **must never be deletable** — they are the fleet's safety net, the targets `routing.md` ships with, and what Settings reset restores. The invariant:
194
+
195
+ - **Builtin-ness is derived, not user-settable.** A role/agent is builtin iff its key exists in `DEFAULT_ROLES` / `DEFAULT_AGENTS`. The API surfaces this as a read-only `builtin: true` marker; a client cannot clear it by writing `builtin: false` (the write paths ignore the field on input and recompute it from the defaults).
196
+ - **Editable, not deletable.** Builtin roles/agents *can* be edited (a role's charter/label/runtime defaults; an agent's expertise/runtime; and — for agents — the display name via rename). They simply **cannot be deleted**. Every delete endpoint (`POST /api/roles/delete`, `POST /api/agents/delete`) hard-refuses a builtin target with a clear error, independent of the reassignment flow.
197
+ - **Reset restores them.** `POST /api/settings/reset` re-seeds the full builtin set even if a user edited them, so a fleet can always recover a known-good roster (mirrors today's `shared.DEFAULT_AGENTS` reset — `dashboard.js:10808-10815`).
198
+ - **No id reuse to dodge the guard.** Because ids are immutable (§2.8), a user cannot "delete then recreate" a builtin id; the delete is simply refused.
199
+
200
+ Only **user-created** roles/agents (keys absent from the defaults) are deletable, subject to the in-use guards in §7.
201
+
202
+ ### 3.3 Two dimensions of an agent: identity + expertise (per-agent) vs. charter (role)
203
+
204
+ An agent's "definition" is **not one blob** — it splits along a **durable vs. variable** seam (§3), surfaced as two dimensions that resolve and render independently. Separating them is what lets the charter be name-agnostic (§4.3c) while each agent stays distinct and knows exactly who it is.
205
+
206
+ 1. **Per-agent — short-form, structured, variable.** The machine-readable card the agent *is aware of*: `name`, `emoji`, role `label`, **`expertise`**, and `id`. The `name`/`emoji`/`expertise` are the agent's own (the **variable** layer — two agents of the same role differ here); `label` is pulled from the role. This is the **single home of the agent's name** and what a rename edits (§5.4). `expertise` is the per-instance specialization.
207
+ 2. **Role — long-form, prose, durable.** The resolved markdown **charter** (`roles[agent.role].charter` ?? legacy `charter.md`). Name-agnostic, second person, refers to peers by role (§4.3c) — the **durable** "what it means to be an X," shared unchanged across every agent of that role.
146
208
 
147
- This preserves every existing resolver's external behavior when `roles` is absent or when `agent.role` is still a free-text string (legacy), and layers the library in additively.
209
+ The system prompt renders the per-agent short-form first, then the role's durable charter — so the agent reads its own name/expertise up top and the shared persona below:
148
210
 
149
- ## 4. Phase 1 — Expose & edit (no new roles/agents)
211
+ ```
212
+ # You are {name} ({label}) ← PER-AGENT short-form. The name lives ONLY here.
213
+ - Agent ID: {id}
214
+ - Emoji: {emoji}
215
+ - Role: {label} ← from the role (durable)
216
+ - Expertise: {agent.expertise} ← VARIABLE, per-agent specialization
217
+ (optionally also emitted as a compact JSON identity card the agent can parse —
218
+ so it can reason about itself structurally, not just read prose)
219
+
220
+ ## Your Charter ← ROLE charter (durable, long-form), name-agnostic
221
+ {resolved role charter}
222
+ ```
223
+
224
+ Why this matters:
225
+
226
+ - **The agent always knows its own name** — via the per-agent short-form — even though the charter prose never names it. "Name-agnostic charter" (§4.3c) does **not** mean "name-blind agent"; the name simply moves to the structured short form.
227
+ - **Rename touches only the per-agent identity** (`name`/`emoji`); neither expertise nor the role charter is affected (§5.4).
228
+ - **Two agents of one role share the durable charter but differ in the variable expertise.** Dallas and Ralph both resolve the Engineer charter, yet Dallas carries `typescript/docker` and Ralph `bug-fixes/scaffolding` — the payoff of the durable/variable split (§3).
229
+
230
+ This formalizes what `buildSystemPrompt` already does loosely today (the `# You are {name}` / `Agent ID` / `Expertise` lines are a proto short-form; `engine/playbook.js:947-955`); the spec elevates it to named dimensions. Whether the short-form is rendered as prose lines or an explicit JSON card is an open question (§9).
231
+
232
+ ## 4. Phase 0 — Prerequisite migrations
233
+
234
+ Foundational, mostly-invisible work that must land before the Library UI. Each item is additive and back-compatible. **Note:** the stable-id formalization (§4.1a) and name-agnostic charters (§4.3c) are **not** owned here — they ship in the [`agent-rename.md`](agent-rename.md) prerequisite and are summarized below for completeness. The two items this spec owns are the `agent-library` flag (§4.0a) and the `skills`→`expertise` field rename (§4.2b).
235
+
236
+ ### 4.0a Feature flag
237
+
238
+ Register `agent-library` (§3.1). No behavior change yet — just the gate other phases reference.
239
+
240
+ ### 4.1a Formalize the stable opaque agent id — *delivered by the prerequisite*
241
+
242
+ > **Owned by [`agent-rename.md`](agent-rename.md) §4.1.** Not re-specified here.
243
+
244
+ The stable-id invariant (id = immutable key, name = display-only) and its guard + regression tests land in the rename prerequisite. The role model below assumes it: an agent can be reassigned to a different role or renamed without touching any id-keyed state (§2.8).
245
+
246
+ ### 4.2b Rename `skills` → `expertise`
247
+
248
+ `agent.skills` is expertise, not skills (§2.3). Rename the field across the stack, with an **unconditional** back-compat reader:
150
249
 
151
- **User story:** "For each of the different roles, I want to see and edit the skill and agent definition."
250
+ - **Read shim (always on):** everywhere that reads agent expertise resolves `agent.expertise ?? agent.skills`. Old configs with `skills` keep working untouched — no disk rewrite forced. (Expertise is the per-agent **variable** layer, §3.3 — it lives on the agent, not the role.)
251
+ - **Writers / defaults:** `DEFAULT_AGENTS` use `expertise`. The Settings write path persists `expertise`; if it sees a legacy `skills` key it may migrate it in-memory (mirroring `applyLegacyCcModelMigration`'s no-rewrite pattern).
252
+ - **Update the two readers** named in §2.3: the `Expertise:` line (`engine/playbook.js:950`) and `pickReReviewAgentHints` (`engine/lifecycle.js:4613-4624`) — both read the resolved expertise, so the soft reviewer-preference heuristic keeps matching `code-review`/`design-review`.
253
+ - **Dashboard:** the Agents table column header and its read/write (`dashboard/js/settings.js:73`, `dashboard.js:10685`) move to `expertise`, accepting `skills` on read for compatibility.
254
+ - **Deprecation:** add a `docs/deprecated.json` entry for the `skills` alias with a cleanup path.
152
255
 
153
- ### 4.1 Scope
256
+ This rename is independent of the role model and can ship first; it is pure renaming + a back-compat alias, so it is safe even with the feature flag OFF (the alias is unconditional).
154
257
 
155
- - A **Role / Agent Library** view in the dashboard listing the existing roles and the agents bound to each.
156
- - For each role: edit its **definition** (markdown persona) and **skills** list.
157
- - For each agent: edit its definition override + skills + runtime (consolidating the Settings table fields and the Charter tab into one place).
258
+ ### 4.3c Name-agnostic charters *delivered by the prerequisite*
259
+
260
+ > **Owned by [`agent-rename.md`](agent-rename.md) §4.2.** Not re-specified here.
261
+
262
+ The rename prerequisite sanitizes the shipped `agents/<id>/charter.md` files to be name-agnostic (second person; peers referred to by role, not name) and adds the content/render tests. This Library reuses that work two ways: (1) it seeds `DEFAULT_ROLES[].charter` from the sanitized text (§5.2), and (2) a role-level charter shared across agents *must* be name-agnostic — which the prerequisite guarantees. The agent still knows its name via the per-agent short-form (§3.3) — name-agnostic ≠ name-blind.
263
+
264
+ ## 5. Phase 1 — Expose & edit (no new roles/agents)
265
+
266
+ **User story:** "For each of the different roles, I want to see and edit the durable charter and per-agent expertise — and rename agents."
267
+
268
+ All UI/API in this phase is gated by `agent-library` (§3.1). The **rename** capability itself comes from the [`agent-rename.md`](agent-rename.md) prerequisite; Phase 1 surfaces it inline in the Library.
269
+
270
+ ### 5.1 Scope
271
+
272
+ - A **Role / Agent Library** view listing existing roles and the agents bound to each.
273
+ - For each role: edit its **charter** (durable markdown persona) and label.
274
+ - For each agent: edit its **expertise** (variable specialization) + runtime, reassign its **role**, and **rename** its display name + emoji (id stays fixed) — consolidating the Settings table fields and the Charter tab into one place.
158
275
  - **No** create/delete of roles or agents in Phase 1.
159
276
 
160
- ### 4.2 Data model (Phase 1)
277
+ ### 5.2 Data model (Phase 1)
161
278
 
162
- - Add `DEFAULT_ROLES` to `engine/shared.js`, derived from the distinct `role` strings in `DEFAULT_AGENTS` (`Lead / Explorer`, `Engineer`, `Analyst`, `Architect`). Seed each role's `definition` from the corresponding agent's existing `charter.md` content where a 1:1 mapping exists; otherwise leave a stub.
163
- - Migrate `agent.role` free-text → role key on first load (in-memory, mirroring `applyLegacyCcModelMigration`'s no-disk-rewrite pattern in shared.js) by slugifying the label and matching against `roles{}`. Unmatched strings remain as legacy free text and are rendered as an ad-hoc role.
164
- - Introduce the resolution rules in §3 behind the existing resolver helpers so engine behavior is unchanged when `roles` is absent.
279
+ - Add `DEFAULT_ROLES` to `engine/shared.js`, one per distinct `role` label in `DEFAULT_AGENTS` (`Lead / Explorer`, `Engineer`, `Analyst`, `Architect`). Author each role's **durable `charter`** name-agnostic (§4.3c), seeded from the sanitized union of the corresponding agents' existing `charter.md` files. Each agent's current per-agent skills become its **`expertise`** (the variable layer), so two Engineers keep distinct specializations while sharing one role charter. (Where two agents of the same label have materially different *personas* today, split them into two roles instead — but the default four collapse cleanly.)
280
+ - Migrate `agent.role` free-text → role key on first load (in-memory, mirroring `applyLegacyCcModelMigration`'s no-disk-rewrite pattern) by slugifying the label and matching against `roles{}`. Unmatched strings remain legacy free text rendered as an ad-hoc role.
281
+ - Resolution rules (§3) sit behind the existing resolver helpers and the feature flag so engine behavior is unchanged when `roles` is absent or the flag is OFF.
165
282
 
166
- ### 4.3 Skills: decide the binding (REQUIRED for this phase)
283
+ ### 5.3 Expertise display
167
284
 
168
- Phase 1 must resolve the "two meanings of skill" ambiguity. Proposed: keep `roles[].skills` / `agent.skills` as **skill references** (tags), and in the system prompt render them as the `Expertise:` line **plus** — when a tag matches a discovered `SKILL.md` name from `getSkills()` surface that skill's description inline. This makes the skills list meaningful without forcing a disk-layout change in P1. (Full per-role skill *scoping* of `collectSkillFiles` is deferred to a later phase; see §7.)
285
+ With §4.2b done, `agent.expertise` renders as the `Expertise:` line and feeds the re-review hint. **Optional P1 enhancement:** when an expertise tag matches a discovered `SKILL.md` name from `getSkills()`, surface that skill's description inline making the list informative without binding skills to agents. Full per-role skill *scoping* of `collectSkillFiles` stays out of scope (§8).
169
286
 
170
- ### 4.4 API (Phase 1)
287
+ ### 5.4 Agent rename — *delivered by the prerequisite; surfaced in the Library*
171
288
 
172
- - `GET /api/roles` `{ roles: { <key>: { label, definition, skills, defaultCli, defaultModel, builtin, agentIds[] } } }`. Aggregates `config.roles` + reverse-maps `config.agents` by role.
173
- - `POST /api/roles/update` → `{ key, definition?, skills?, defaultCli?, defaultModel? }`. Edits an existing role only (P1). Writes through `mutateJsonFileLocked` on `config.json` (never `safeWrite` — see CLAUDE.md concurrency rules).
174
- - Extend `POST /api/settings` agent loop to accept `definitionOverride`.
175
- - Reuse/retire `POST /api/agents/charter`: in P1 it continues to write `agents/<id>/charter.md` for backward-compat, but the library writes `definitionOverride` into config; the resolver prefers the override. (Deprecation tracked in `docs/deprecated.json`.)
289
+ > **Full contract in [`agent-rename.md`](agent-rename.md) §4.3.** Not re-specified here.
176
290
 
177
- ### 4.5 UI (Phase 1)
291
+ The agent **rename** capability (`POST /api/agents/rename` → `{ id, name, emoji? }`: validation, idempotency, duplicate-name soft-warning, in-flight safety, cache invalidation, stale-reference behavior, audit log) is owned by the prerequisite and may already be enabled (behind its own `agent-rename` flag) before this Library ships. Phase 1 does **not** redefine it — it simply **surfaces the existing endpoint inline** in the Library (§5.6) so renaming sits next to role/expertise editing. Recap of what callers rely on: rename changes only the display `name`/`emoji`; every id-keyed state (charter/memory/metrics/dispatch/PR/routing/inbox) is untouched (§2.8); a running dispatch is never killed.
178
292
 
179
- - New **Library** entry on the Settings rail (or a top-level sidebar page — decide in review). Layout: left column = role list; right pane = selected role's definition editor (markdown textarea with preview, reusing the Charter tab's editor pattern from `detail-panel.js`) + skills chips + the agents bound to that role (each expandable to its per-agent overrides).
180
- - Definition editor reuses the existing markdown view/edit toggle and `renderMd()` escaping already used by the Charter tab (`dashboard/js/detail-panel.js:115-126`) to satisfy the no-unsanitized lint gate (`npm run lint`).
181
- - Settings → Agents table gains a link/affordance into the Library for the definition (so the two surfaces don't drift).
293
+ ### 5.5 API (Phase 1)
182
294
 
183
- ### 4.6 Phase 1 acceptance criteria
295
+ - `GET /api/roles` → `{ roles: { <key>: { label, charter, defaultCli, defaultModel, builtin, agentIds[] } } }`. Aggregates `config.roles` + reverse-maps `config.agents` by role. Flag-gated.
296
+ - `POST /api/roles/update` → `{ key, label?, charter?, defaultCli?, defaultModel? }`. Edits an existing role's **durable charter** + label + runtime defaults only (P1). Writes through `mutateJsonFileLocked` on `config.json` (never `safeWrite` — CLAUDE.md concurrency rules).
297
+ - `POST /api/agents/rename` → `{ id, name, emoji? }` — **owned by the [`agent-rename.md`](agent-rename.md) prerequisite** (§5.4); the Library calls it, it is not re-specified here.
298
+ - Extend `POST /api/settings` agent loop to accept the agent's **`expertise`** (variable layer) and **`role`** reassignment.
299
+ - `POST /api/agents/charter` is **superseded** by role charters: it still writes `agents/<id>/charter.md` (read only as the legacy fallback, §6), but the Library edits the role's `charter` instead. (Deprecation tracked in `docs/deprecated.json`.)
184
300
 
185
- 1. Library view lists every existing role with its definition + skills, and the agents under each.
186
- 2. Editing a role's definition changes the system prompt for every agent bound to that role (verified by `buildSystemPrompt` output).
187
- 3. Editing an agent's definition override wins over the role definition for that agent only.
188
- 4. Engine behavior is **unchanged** when `config.roles` is absent (back-compat).
189
- 5. All writes go through `mutateJsonFileLocked`; no `safeWrite` on `config.json`.
190
- 6. `npm test` green (add source-inspection + resolver unit tests near existing agent/charter tests); `npm run lint` green.
191
- 7. Settings reset still restores a working fleet (now `DEFAULT_ROLES` + `DEFAULT_AGENTS`).
301
+ ### 5.6 UI (Phase 1)
192
302
 
193
- ## 5. Migration & backward compatibility
303
+ - New **Library** entry on the Settings rail (or a top-level sidebar page — decide in review), shown only when `agent-library` is ON. Layout: left column = role list; right pane = selected role's **charter** editor (markdown textarea + preview, reusing the Charter tab editor pattern from `detail-panel.js`) + the agents bound to that role (each expandable to its per-agent **expertise** chips, role reassignment, runtime, and an inline **rename** field for the display name + emoji).
304
+ - The inline rename field reuses the prerequisite's `POST /api/agents/rename` endpoint with the same inline validation + soft duplicate-name warning (agent-rename.md §4.3); on save it optimistically updates the team/detail display.
305
+ - Charter editors reuse the existing markdown view/edit toggle and `renderMd()` / `escHtml()` escaping already used by the Charter tab (`dashboard/js/detail-panel.js:115-126`) to satisfy the no-unsanitized lint gate (`npm run lint`).
306
+ - Settings → Agents table gains a link/affordance into the Library (so the two surfaces don't drift).
194
307
 
195
- - **Additive config.** `roles` is new and optional. Absent `roles` legacy path (free-text role label, per-agent `charter.md`). No forced disk rewrite — migration is in-memory at load, matching `applyLegacyCcModelMigration`.
196
- - **Charters preserved.** `agents/<id>/charter.md` remains readable and is the lowest-priority definition source. `minions init`/upgrade already preserves charter files (`bin/minions.js:1361-1368`) — do not regress that.
197
- - **Routing unaffected in P1/P2-by-agent.** Routing still targets agent ids. (Routing-by-role is explicitly out of scope; see §7.)
198
- - **Deprecation.** If `POST /api/agents/charter` is superseded by the library's `definitionOverride`, add a `docs/deprecated.json` entry with a cleanup path.
308
+ ### 5.7 Phase 1 acceptance criteria
199
309
 
200
- ## 6. Phase 2 Create & assign
310
+ 1. With the flag ON, the Library lists every role with its charter, and the agents (with their per-agent expertise) under each.
311
+ 2. Editing a role's charter changes the system prompt for **every** agent bound to that role (verified by `buildSystemPrompt` output) — the durable layer is shared.
312
+ 3. Two agents of the same role render the **same** charter but their **own** expertise; editing one agent's expertise does not affect the other (the variable layer is per-agent).
313
+ 4. The Library surfaces the prerequisite's rename inline and a rename performed from it behaves per [`agent-rename.md`](agent-rename.md) §6 (display-only change; id-keyed state untouched; in-flight-safe).
314
+ 5. With the `agent-library` flag OFF and no `roles`, engine + dashboard behavior is byte-for-byte identical to today.
315
+ 6. All writes go through `mutateJsonFileLocked`; no `safeWrite` on `config.json`.
316
+ 7. `npm test` green (resolver + durable/variable split tests near existing agent/charter tests); `npm run lint` green.
317
+ 8. Settings reset still restores a working fleet (now `DEFAULT_ROLES` + `DEFAULT_AGENTS`).
318
+
319
+ ## 6. Migration & backward compatibility
320
+
321
+ - **Feature-flagged & dark by default.** New surfaces are OFF until `agent-library` is enabled (§3.1). Back-compat readers are unconditional so toggling the flag off after edits never breaks a fleet.
322
+ - **Additive config.** `roles` (with `charter`) and agent `expertise` are new and optional. Absent ⇒ legacy path (free-text role, `skills`, per-agent `charter.md`). No forced disk rewrite — migration is in-memory at load, matching `applyLegacyCcModelMigration`.
323
+ - **`skills` alias preserved.** Reads resolve `agent.expertise ?? agent.skills`; legacy configs are untouched (§4.2b).
324
+ - **Charters preserved.** `agents/<id>/charter.md` remains readable as the **lowest-priority** charter source (used only when an agent's role has no charter). `minions init`/upgrade already preserves charter files (`bin/minions.js:1361-1368`) — do not regress that.
325
+ - **Name decoupling via the prerequisite.** Name-agnostic charters and the stable-id invariant ship in [`agent-rename.md`](agent-rename.md) (§4.1a, §4.3c). This Library assumes them: role charters are shared name-agnostic prose, and agents are reassigned/renamed without re-keying any state. Legacy user charters with names render verbatim (non-breaking).
326
+ - **Routing unaffected.** Routing still targets agent ids. (Routing-by-role is out of scope; see §8.)
327
+ - **Deprecation.** `skills`→`expertise` alias and any superseded `/api/agents/charter` usage get `docs/deprecated.json` entries with cleanup paths.
328
+
329
+ ## 7. Phase 2 — Create & assign
201
330
 
202
331
  **User story:** "Add new roles, and assign new named agents with these roles."
203
332
 
204
- ### 6.1 Scope
333
+ Flag-gated like Phase 1.
334
+
335
+ ### 7.1 Scope
205
336
 
206
- - **Role CRUD:** create/rename/delete roles in the library. Deleting a role is blocked while agents reference it (or offers reassignment). `builtin` roles are delete-protected.
207
- - **Agent CRUD:** create a new named agent (id + name + emoji + role + optional overrides) and delete a non-builtin agent.
337
+ - **Role CRUD:** create/rename/delete **user-created** roles. Builtin roles (seeded from `DEFAULT_ROLES`) are editable but **never deletable** (§3.2). Deleting a user-created role is additionally blocked while agents reference it (or offers reassignment).
338
+ - **Agent CRUD:** create a new named agent (id + name + emoji + role + optional overrides) and delete a **user-created** agent. Builtin agents (seeded from `DEFAULT_AGENTS`) are **never deletable** (§3.2).
208
339
  - New agents become immediately routable (they appear to `resolveAgent` via `config.agents`) and selectable in `routing.md` (manual) — auto-wiring into routing is out of scope.
209
340
 
210
- ### 6.2 Data model / engine
341
+ ### 7.2 Data model / engine
211
342
 
212
- - New agent ids validated: lowercase slug, unique, not colliding with `temp-*` reserved prefix.
213
- - `resolveAgent` already iterates `config.agents` generically, so new agents are picked up with no engine change (`engine/routing.js:192-249`). Deleting an agent must also: cancel/skip pending dispatches targeting it, and leave its history intact (mirror the conservative posture of project removal in `engine/projects.js`).
214
- - A new agent with no `charter.md` and no `definitionOverride` inherits its role's definition this is the payoff of §3.
343
+ - New agent ids validated: lowercase slug, unique, not colliding with `temp-*` reserved prefix, and **immutable thereafter** (§2.8).
344
+ - `resolveAgent` already iterates `config.agents` generically, so new agents are picked up with no engine change (`engine/routing.js:192-249`). Deleting an agent must also cancel/skip pending dispatches targeting it and leave its history intact (mirror the conservative posture of project removal in `engine/projects.js`).
345
+ - A new agent with no `charter.md` inherits its role's **durable charter** automatically the payoff of §3 — and carries its own **variable expertise**.
215
346
 
216
- ### 6.3 API (Phase 2)
347
+ ### 7.3 API (Phase 2)
217
348
 
218
- - `POST /api/roles/create` `{ key, label, definition, skills, defaultCli?, defaultModel? }`.
219
- - `POST /api/roles/delete` `{ key, reassignTo? }` — refuses if agents reference it and no `reassignTo` given; refuses on `builtin`.
220
- - `POST /api/agents/create` `{ id, name, emoji, role, overrides? }`.
221
- - `POST /api/agents/delete` `{ id }` — refuses on builtin; drains/cancels pending dispatches first.
222
- - Extend `POST /api/settings/reset` semantics: reset clears user-created roles/agents back to `DEFAULT_ROLES`/`DEFAULT_AGENTS`.
349
+ - `POST /api/roles/create` `{ key, label, charter, defaultCli?, defaultModel? }` — the durable definition; no expertise (expertise is per-agent).
350
+ - `POST /api/roles/delete` `{ key, reassignTo? }` — **hard-refuses builtin roles** (§3.2); for user-created roles, refuses if agents reference it and no `reassignTo` is given.
351
+ - `POST /api/agents/create` `{ id, name, emoji, role, expertise?, overrides? }`.
352
+ - `POST /api/agents/delete` `{ id }` — **hard-refuses builtin agents** (§3.2); for user-created agents, drains/cancels pending dispatches first.
353
+ - Extend `POST /api/settings/reset` so reset removes user-created roles/agents and re-seeds the full builtin set (`DEFAULT_ROLES`/`DEFAULT_AGENTS`).
223
354
 
224
- ### 6.4 UI (Phase 2)
355
+ ### 7.4 UI (Phase 2)
225
356
 
226
- - "+ New role" and "+ New agent" affordances in the Library.
227
- - Agent creation form (id/name/emoji/role picker + optional runtime overrides).
357
+ - "+ New role" (label + charter editor) and "+ New agent" affordances in the Library.
358
+ - Agent creation form (id/name/emoji/role picker + expertise + optional runtime overrides).
228
359
  - Delete with confirmation + reassignment flow for roles in use.
229
360
 
230
- ### 6.5 Phase 2 acceptance criteria
361
+ ### 7.5 Phase 2 acceptance criteria
231
362
 
232
- 1. A user can create a role with a definition + skills and see it in the library.
233
- 2. A user can create a named agent bound to a role; with no override it inherits the role definition; the engine can route work to it.
234
- 3. Deleting a role in use is blocked or forces reassignment; builtin roles/agents cannot be deleted.
363
+ 1. A user can create a role with a durable charter and see it in the library.
364
+ 2. A user can create a named agent bound to a role; it inherits the role's charter and carries its own expertise; the engine can route work to it.
365
+ 3. **Builtin roles and agents (seeded from the defaults) cannot be deleted** — every delete endpoint hard-refuses them, and the `builtin` marker cannot be cleared by a client write (§3.2). Deleting a *user-created* role in use is blocked or forces reassignment.
235
366
  4. Deleting an agent cancels its pending dispatches and preserves completed history.
236
367
  5. Settings reset returns to the default fleet.
237
- 6. `npm test` + `npm run lint` green; new CRUD covered by unit tests; concurrency via `mutateJsonFileLocked`.
368
+ 6. `npm test` + `npm run lint` green; CRUD covered by unit tests; concurrency via `mutateJsonFileLocked`.
238
369
 
239
- ## 7. Out of scope (explicitly deferred)
370
+ ## 8. Out of scope (explicitly deferred)
240
371
 
241
- - **Routing-by-role** (e.g. `routing.md` rows targeting a role instead of an agent id). Routing stays agent-id-based.
242
- - **Per-role/per-agent skill *scoping*** of on-disk `SKILL.md` discovery (`collectSkillFiles` stays global/project/plugin). P1 only makes the skills *list* meaningful in the prompt (§4.3).
372
+ - **Agent rename, stable-id, and name-agnostic charters** owned by the [`agent-rename.md`](agent-rename.md) prerequisite, not this spec (re-keying an id remains out of scope there too).
373
+ - **Routing-by-role** (`routing.md` rows targeting a role instead of an agent id). Routing stays agent-id-based.
374
+ - **Binding real `SKILL.md` files to roles/agents** — `collectSkillFiles` stays global/project/plugin. The freed-up "skills" term is reserved for this future work; P1 only makes the *expertise* list informative (§5.3).
243
375
  - **Per-project role overrides** (`projects/<name>/...`).
244
376
  - **Importing/sharing role libraries** across installs.
245
- - Any change to the meeting/plan agent-charter linkage beyond reading the new definition source.
377
+ - Any change to the meeting/plan agent-charter linkage beyond reading the new role-charter source.
246
378
 
247
- ## 8. Open questions (resolve in review)
379
+ ## 9. Open questions (resolve in review)
248
380
 
249
381
  1. **Library placement** — Settings rail entry vs. a dedicated top-level sidebar page?
250
- 2. **Role↔agent cardinality framing** — do we surface roles as the primary list (agents nested under them), or keep agents primary with role as an attribute? (Spec assumes roles-primary in the Library; agents-primary stays in Settings.)
251
- 3. **Charter vs definition naming** — adopt "definition" everywhere, or keep "charter" as the user-facing term to avoid churn in existing UI/tests?
252
- 4. **Skills binding4.3)** — ship the "tag matches discovered SKILL.md → inline description" behavior in P1, or keep skills purely as the `Expertise:` line until a later phase?
253
- 5. **Default role definitions** — seed from existing per-agent `charter.md`, or author fresh role-level definitions?
254
-
255
- ## 9. Work breakdown (suggested PRD seeds)
256
-
257
- **Phase 1**
258
- - P1-a: `DEFAULT_ROLES` + in-memory legacy role migration + resolver tiering (engine/shared.js, playbook.js).
259
- - P1-b: `GET /api/roles`, `POST /api/roles/update`, `definitionOverride` on `POST /api/settings` (dashboard.js).
260
- - P1-c: Library UI (role list + definition editor + skills + nested agents) reusing the charter editor pattern.
261
- - P1-d: Tests (resolver unit tests, source-inspection for endpoints/UI) + `docs/deprecated.json` entry if `/api/agents/charter` is superseded + settings parity note in CLAUDE.md.
262
-
263
- **Phase 2**
264
- - P2-a: Role CRUD API + delete-guard/reassignment.
265
- - P2-b: Agent CRUD API + dispatch-drain on delete + id validation.
266
- - P2-c: Library create/delete UI flows.
267
- - P2-d: Tests + reset semantics + docs.
382
+ 2. **Role↔agent cardinality framing** — surface roles as the primary list (agents nested), or keep agents primary with role as an attribute? (Spec assumes roles-primary in the Library; agents-primary stays in Settings.)
383
+ 3. **"Charter" terminology** — the spec uses **`charter`** as the durable role-level field name (matching today's `charter.md` and the existing Charter tab). Confirm we keep "charter" user-facing everywhere rather than introducing "definition" as a competing term. *(Tentatively resolved: yes, `charter` on the role; "definition" used only as the generic English word.)*
384
+ 4. **Expertise→skill enrichment5.3)** — ship the "tag matches discovered SKILL.md → inline description" behavior in P1, or keep expertise purely as the `Expertise:` line until later?
385
+ 5. **Default role charters** — author fresh name-agnostic role charters, or mechanically sanitize + merge the existing per-agent `charter.md` files? And how to handle two builtin agents sharing a label but differing in persona today (split into two roles vs. unify)?
386
+ 6. **`skills`→`expertise` rollout** — ship the §4.2b rename as its own standalone PR (pure rename + alias, flag-independent) ahead of the Library, to de-risk the bigger change?
387
+ 7. **Identity rendering (§3.3)** render the per-agent short-form as the existing prose lines (`# You are …` / `Agent ID` / `Expertise`), or also emit a compact machine-readable JSON identity card the agent can parse to reason about itself? (Prose is the lowest-risk default; JSON is additive.)
388
+
389
+ ## 10. Work breakdown (suggested PRD seeds)
390
+
391
+ **Prerequisite (separate spec) [`agent-rename.md`](agent-rename.md)**
392
+ - Stable-id formalization, name-agnostic charters, and the `POST /api/agents/rename` capability ship there (its `AR-a…AR-f` breakdown). This Library's Phase 1 depends on it.
393
+
394
+ **Phase 0 — prerequisites (owned here)**
395
+ - P0-a: Register `agent-library` feature flag (`engine/features.js`) + Settings toggle.
396
+ - P0-b: Rename `skills`→`expertise` with unconditional back-compat alias; update `playbook.js:950`, `lifecycle.js:4613-4624`, `dashboard.js`/`settings.js`, `DEFAULT_AGENTS`; `docs/deprecated.json` entry.
397
+
398
+ **Phase 1 expose & edit**
399
+ - P1-a: `DEFAULT_ROLES` (durable charter per role) + per-agent `expertise` (variable) + in-memory legacy role migration + role-tier resolver (engine/shared.js, playbook.js), flag-gated. Seeds role charters from the prerequisite's sanitized `agents/<id>/charter.md`.
400
+ - P1-b: `GET /api/roles`, `POST /api/roles/update` (charter/label/runtime), agent `expertise`/`role` on `POST /api/settings` (dashboard.js). (Rename endpoint comes from the prerequisite.)
401
+ - P1-c: Library UI (role list + role-charter editor + nested agents with per-agent expertise chips + role reassignment + inline rename surfacing the prerequisite's endpoint) reusing the charter editor pattern, flag-gated.
402
+ - P1-d: Tests (resolver, durable/variable split, source-inspection) + `docs/deprecated.json` updates + settings-parity note in CLAUDE.md.
403
+
404
+ **Phase 2 — create & assign**
405
+ - P2-a: Role CRUD API + **builtin delete-protection (§3.2)** + user-role delete-guard/reassignment.
406
+ - P2-b: Agent CRUD API + **builtin delete-protection (§3.2)** + dispatch-drain on delete + id validation.
407
+ - P2-c: Library create/delete UI flows (delete affordance hidden/disabled for builtin).
408
+ - P2-d: Tests (incl. builtin-undeletable assertions + `builtin: false` write-ignore) + reset semantics + docs.
268
409
 
269
410
  ---
270
411
 
271
- _Every new `engine.*`/config surface introduced here must also get a dashboard control and be documented in CLAUDE.md — config without UI is incomplete (see CLAUDE.md "Best Practices" #15)._
412
+ _Every new `engine.*`/config surface introduced here must also get a dashboard control and be documented in CLAUDE.md — config without UI is incomplete (see CLAUDE.md "Best Practices" #15). New feature flags follow the FEATURES-registry lifecycle: register with `addedIn`/`expires`, gate, then delete the entry + gates on graduation._
@@ -0,0 +1,163 @@
1
+ # Spec: Agent Rename & Name Decoupling
2
+
3
+ **Status:** Draft
4
+ **Owner:** @calebt
5
+ **Audience:** Minions contributors working on the engine config surface, playbook/system-prompt rendering, and the dashboard agents UI.
6
+ **Prerequisite for:** [`agent-configurability.md`](agent-configurability.md) — the Role / Agent Library builds on the decoupling delivered here.
7
+
8
+ > Companion reading: [`.github/copilot-instructions.md`](../../.github/copilot-instructions.md) and [`CLAUDE.md`](../../CLAUDE.md) for the engine/dashboard split and config conventions.
9
+
10
+ ## 1. Motivation
11
+
12
+ You cannot rename an agent today. Worse, an agent's display name is quietly **load-bearing**: it's baked into the shipped `charter.md` prose, and the engine matches some referenced artifacts by agent *name*. Before the fleet can become user-configurable (reusable roles, new named agents — see [`agent-configurability.md`](agent-configurability.md)), names must stop being structurally significant.
13
+
14
+ This spec delivers exactly that decoupling, in three parts:
15
+
16
+ 1. **Identity decoupling** — formalize the agent's **id** as a stable opaque key, distinct from its display name, so renaming touches a single field and zero state.
17
+ 2. **Content decoupling** — make charters **name-agnostic**, so the persona never restates a proper name (which a rename would invalidate).
18
+ 3. **The rename capability** — a `POST /api/agents/rename` endpoint + UI that changes the display name (and emoji) safely.
19
+
20
+ Shipping this first de-risks the larger Role / Agent Library: once names aren't load-bearing, roles can be reused across agents and agents can be renamed/created without name collisions mattering.
21
+
22
+ ## 2. Current state (grounding)
23
+
24
+ Citations are `file:line` against the repo at spec-authoring time.
25
+
26
+ ### 2.1 Agents are already id-keyed
27
+
28
+ Agents live under `config.agents` keyed by a **stable lowercase id** (the object key). `name` is a **separate display field** — there is no explicit `id` field inside the object; the key *is* the id, and `getAgents()` materializes it as `{ id, ...info }` (`engine/queries.js:681-687`).
29
+
30
+ ```js
31
+ // engine/shared.js:4398-4404
32
+ const DEFAULT_AGENTS = {
33
+ ripley: { name: 'Ripley', emoji: '🏗️', role: 'Lead / Explorer', skills: [...] },
34
+ dallas: { name: 'Dallas', emoji: '🔧', role: 'Engineer', skills: [...] },
35
+ // …
36
+ };
37
+ ```
38
+
39
+ Crucially, **nothing derives the id from the name** — `id === lowercase(name)` is coincidental, not enforced. Routing only lowercases routing-table cells, not names (`engine/routing.js:31-52`); agent lookup is by key with case-insensitive normalization against existing keys (`engine/routing.js:160-189`). So `name !== lowercase(id)` is already safe — the foundation that makes **rename = change `name`, keep `id`** trivial.
40
+
41
+ ### 2.2 Every agent-scoped state is keyed by id
42
+
43
+ | State | Keyed by id at |
44
+ |-------|----------------|
45
+ | Charter file | `agents/<id>/charter.md` (`engine/queries.js:677-679`) |
46
+ | Per-agent memory | `knowledge/agents/<id>.md`; inbox routes by `agent:` frontmatter → filename prefix (`engine/consolidation.js:106-129,159-182`) |
47
+ | Metrics / spend / error rate | `metrics[id]`, `perAgent[id]` (`engine/routing.js:66-92`) |
48
+ | Dispatch records | `dispatch.active[].agent` (`engine/queries.js:587-605`) |
49
+ | PR records | `pr.agent` (`engine/queries.js:431-446`) |
50
+ | Inbox files | `notes/inbox/<id>-…` filename prefix (`engine/queries.js:700-713`) |
51
+ | Routing | preferred/fallback agent ids (`routing.md`, `engine/routing.js:31-52`) |
52
+
53
+ The **only** consumers of the display `name` are `buildSystemPrompt`'s `# You are {name}` line (`engine/playbook.js:947-950`) and dashboard display text. So renaming the display name is safe and needs no state migration **as long as the id key is never mutated**. Changing the id key would orphan every row above — the id must be immutable.
54
+
55
+ ### 2.3 Names are load-bearing in two places (the part that must change)
56
+
57
+ 1. **Charters hardcode names.** The shipped charters bake the agent's own name and peers' names into the prose — e.g. `agents/ripley/charter.md` opens `# Ripley — Lead / Explorer`, `**Name:** Ripley`, and says "Handing off structured findings to **Dallas** (Engineer) and **Lambert** (Analyst)". A rename would strew the old name through the prompt.
58
+ 2. **Playbook context-resolution matches by name.** `engine/playbook.js:163-240` matches referenced plans/notes by **both** agent `id` **and** `name` (e.g. "Ripley's plan"). After a rename, artifacts that referenced the old *name* stop auto-linking (id-based matches still resolve).
59
+
60
+ These are the two reasons "names are load-bearing" — §4.2 and §4.3 neutralize them.
61
+
62
+ ## 3. Feature flag
63
+
64
+ - Register `agent-rename` in the `FEATURES` registry (`engine/features.js`) as `{ description, default: false, addedIn, expires }`.
65
+ - **Engine/dashboard** gate the rename **endpoint + UI** with `features.isFeatureOn('agent-rename', config)` / `MinionsFeatures.isOn('agent-rename')`.
66
+ - Toggle via Settings → Experimental flags, `config.features['agent-rename']: true`, or `MINIONS_FEATURE_AGENT_RENAME=1`. Resolution order: env → `config.features` → registry default (default OFF).
67
+ - **What the flag gates:** the `POST /api/agents/rename` endpoint and the rename UI affordance.
68
+ - **What the flag does NOT gate (always on — pure back-compat hygiene):** the stable-id guard/tests (§4.1, invisible) and the name-agnostic **default** charters (§4.2 — a behavior-equivalent content change to the shipped roster). These ship unconditionally so the foundation is in place whether or not rename is enabled; legacy user charters are never touched.
69
+
70
+ When the feature graduates, delete the registry entry + gate (per CLAUDE.md feature-flag lifecycle).
71
+
72
+ ## 4. The work
73
+
74
+ ### 4.1 Formalize the stable opaque agent id
75
+
76
+ Agents are **already** id-keyed (§2.1–2.2); this step makes the invariant explicit and rename-safe:
77
+
78
+ - Document the invariant: the `config.agents` key is the agent's immutable id; the display name lives in `name`.
79
+ - Add a guard so no write path mutates an id key. The rename path (§4.3) changes `name`/`emoji` only; there is deliberately **no** "change id" path. Re-keying would be a dedicated future migration (move `agents/<id>/`, `knowledge/agents/<id>.md`, metrics buckets, rewrite `routing.md`/dispatch/PR refs) — explicitly out of scope (§7).
80
+ - Add unit tests asserting `name !== lowercase(id)` round-trips through `buildSystemPrompt`, `getAgents`, routing, charter/memory paths, and metrics without breakage (most already pass — they lock the invariant against regressions).
81
+
82
+ ### 4.2 Make charters name-agnostic
83
+
84
+ A charter that hardcodes a proper name (§2.3) breaks the moment an agent is renamed (and later blocks a role-level charter being shared across agents — the configurability payoff). Make the charter body **name-agnostic** so the identity line is the single source of the name:
85
+
86
+ - **Single source of name.** The agent's name appears *only* in the engine-injected `# You are {name} ({label})` block (`engine/playbook.js:947-950`). The charter prose must use the **second person** ("you", "your") and **never** restate its own proper name (no `# Ripley`, no `**Name:** Ripley`). The agent still knows its name — it reads it from the identity block — so **name-agnostic ≠ name-blind**.
87
+ - **Refer to peers by role, not name.** "hand off to **Dallas** (Engineer)" becomes "hand off to the **Engineer** role" — roles are stable; names are mutable. This keeps a charter valid when *any* agent is renamed.
88
+ - **Rewrite the shipped defaults.** Sanitize the repo's `agents/<id>/charter.md` files name-agnostic. (The configurability spec later seeds role charters from these — but the sanitization lands here, unconditionally.)
89
+ - **Legacy user charters are not auto-rewritten.** A user-authored `charter.md` that still contains a name renders verbatim (back-compat, non-breaking) — it just reads stale after a rename. A **soft, dismissible warning** flags a name embedded in an editable charter, nudging toward second person / role references. No hard block, no silent rewrite of user content.
90
+ - **Tests.** Content check that shipped default charters contain no agent proper names; render test that `buildSystemPrompt` after a rename contains the **new** name and **not** the old one anywhere in the prompt.
91
+
92
+ ### 4.3 The rename capability
93
+
94
+ A rename changes the agent's **display identity** (`name`, and optionally `emoji`); the id key is never touched (§4.1).
95
+
96
+ **`POST /api/agents/rename` → `{ id, name, emoji? }`** (flag-gated, §3)
97
+
98
+ - **Validation:** `name` required, trimmed, non-empty, length-capped (e.g. ≤ 64), may contain Unicode/emoji; control chars rejected. `emoji` optional. Missing `id` → 4xx. Same-name is an idempotent no-op (still 2xx). Builtin agents are renamable (rename ≠ delete).
99
+ - **Uniqueness:** names need **not** be unique (id is the only key), but a collision with another agent's current display name returns a **soft warning** in the response (operator-confusion risk), not a hard block; the UI surfaces it and lets the user proceed.
100
+ - **Concurrency & audit:** writes through `mutateJsonFileLocked` on `config.json` (never `safeWrite`); logs `old → new`.
101
+
102
+ **What updates (display only, picked up live)**
103
+ - The system-prompt identity line `# You are {name} ({label})` on the **next** dispatch (`engine/playbook.js:947-950`).
104
+ - Dashboard display: team cards, agent detail panel, history attribution, Command Center state preamble — all read `name` live on next render.
105
+ - The rename invalidates the system-prompt / status / CC-preamble caches so the new name surfaces promptly.
106
+
107
+ **What does NOT change (id-keyed, untouched — §2.2)**
108
+ - Charter/memory paths, metrics/spend/error-rate buckets, dispatch records, PR `agent` refs, inbox prefixes, routing.
109
+ - **Author matching is id-based:** `_author_` routing resolves to the author *agent id* and `pr.agent` groups by id (`engine/queries.js:431-446`), so rename never breaks self-review bans or PR-author follow-ups.
110
+
111
+ **In-flight dispatches**
112
+ - A currently-running agent finishes under the system prompt it was **already spawned with** — rename does **not** kill or re-spawn it. The new name applies to the **next** dispatch. Live-output, completion reports, and history are id-keyed, so they attribute correctly throughout.
113
+
114
+ **Stale name references in historical free-text (known, accepted, non-breaking)**
115
+ - Rename does **not** cascade into pre-existing free-text: `notes.md`, `pinned.md`, the knowledge base, per-agent memory, and plan/PRD prose keep the old name (non-breaking — just stale).
116
+ - Concrete engine consequence (§2.3): playbook context-resolution matches referenced plans/notes by both `id` and `name` (`engine/playbook.js:163-240`); after a rename, old-name artifacts stop auto-linking to the agent (id matches still resolve). §4.2's guidance minimizes future name-baking.
117
+ - A **soft, non-blocking** "old name still appears in N place(s)" hint may follow a rename; it **never** auto-rewrites user/historical content.
118
+
119
+ **UI**
120
+ - The rename affordance lands in the existing surfaces so it's usable before the Library exists: the **Settings → Agents table** first column (Agent) and/or the **agent detail panel** (next to the Charter tab editor it already hosts — `dashboard/js/detail-panel.js:115-126`).
121
+ - Inline validation (non-empty, length) + the soft duplicate-name warning before submit; on save it calls `POST /api/agents/rename` and optimistically updates the team/detail display. Reuse `escHtml()` to satisfy the no-unsanitized lint gate (`npm run lint`).
122
+
123
+ ## 5. Migration & backward compatibility
124
+
125
+ - **Feature-flagged rename, unconditional foundation.** The rename endpoint + UI are OFF until `agent-rename` is enabled (§3). The stable-id guard (§4.1) and name-agnostic **default** charters (§4.2) ship unconditionally as back-compat hygiene.
126
+ - **Stable id.** The id key is immutable; rename touches `name`/`emoji` only. No agent state is re-keyed.
127
+ - **Charters preserved.** `agents/<id>/charter.md` remains the charter source. `minions init`/upgrade already preserves charter files (`bin/minions.js:1361-1368`) — do not regress that. Legacy user charters with names render verbatim and only earn a soft warning.
128
+ - **Routing unaffected.** Routing targets agent ids; rename is invisible to it.
129
+ - **No `name` writer today is broken.** `POST /api/settings` does not edit `name` (`dashboard.js:10672-10758`); the new rename endpoint is the sole `name` writer.
130
+
131
+ ## 6. Acceptance criteria
132
+
133
+ 1. `POST /api/agents/rename` changes only `name` (and optional `emoji`); id key, charter/memory paths, metrics, dispatch/PR refs, routing, and inbox prefixes are untouched (verified by §4.1 tests).
134
+ 2. After a rename, the rendered system prompt contains the **new** name and the **old** name appears **nowhere** — because the default charters are name-agnostic and the name comes only from the injected identity line.
135
+ 3. Validation holds: empty/whitespace rejected; over-length rejected; same-name no-op; duplicate display name → soft warning but allowed; missing id → 4xx; builtin agents renamable.
136
+ 4. Renaming an agent with a live dispatch does **not** kill/re-spawn it; the running agent finishes under its already-built prompt and the new name applies next dispatch.
137
+ 5. With `agent-rename` OFF, the endpoint/UI are absent and behavior is identical to today; the name-agnostic default charters and stable-id guard are present regardless.
138
+ 6. All writes go through `mutateJsonFileLocked`; no `safeWrite` on `config.json`.
139
+ 7. `npm test` green (stable-id invariant, rename validation + in-flight, name-agnostic content/render tests near existing agent/charter tests); `npm run lint` green.
140
+
141
+ ## 7. Out of scope
142
+
143
+ - **Re-keying an agent id.** Rename changes `name`/`emoji` only; there is no change-id path (§4.1).
144
+ - **The Role / Agent Library** itself (roles, durable charters, expertise field, CRUD) — see [`agent-configurability.md`](agent-configurability.md), which depends on this spec.
145
+ - **`skills`→`expertise` field rename** — a separate cleanup owned by the configurability spec.
146
+ - **Cascading a rename into historical free-text** (notes/KB/memory/plans). Non-goal; only a soft hint is offered.
147
+
148
+ ## 8. Work breakdown (suggested PRD seeds)
149
+
150
+ - AR-a: Register `agent-rename` feature flag (`engine/features.js`) + Settings toggle.
151
+ - AR-b: Formalize stable-id invariant + rename-safety guard + regression tests (`engine/shared.js`, `engine/queries.js`, tests). *(Unconditional.)*
152
+ - AR-c: Sanitize shipped `agents/<id>/charter.md` to be name-agnostic (second person, peers-by-role) + content test (no proper names) + post-rename render test. *(Unconditional.)*
153
+ - AR-d: `POST /api/agents/rename` (full §4.3 contract: name+emoji, validation, idempotency, duplicate-warning, cache invalidation, in-flight safety, audit log) — flag-gated.
154
+ - AR-e: Rename UI in the Settings agents table / detail panel (inline validation + soft duplicate-name warning) — flag-gated; soft name-in-charter warning.
155
+ - AR-f: Tests (rename validation + in-flight + stale-reference behavior, name-agnostic render) + settings-parity note in CLAUDE.md.
156
+
157
+ ## 9. Relationship to Agent & Role Configurability
158
+
159
+ This spec is a **hard prerequisite** for [`agent-configurability.md`](agent-configurability.md). It delivers the "names aren't load-bearing" foundation — stable opaque id, name-agnostic charters, and the rename capability — that the Role / Agent Library relies on to make roles reusable and to stand up new named agents without name collisions mattering. The configurability spec references this work rather than redefining it.
160
+
161
+ ---
162
+
163
+ _Every new config surface here must also get a dashboard control and be documented in CLAUDE.md — config without UI is incomplete (CLAUDE.md "Best Practices" #15). The `agent-rename` flag follows the FEATURES-registry lifecycle: register with `addedIn`/`expires`, gate, then delete the entry + gate on graduation._
@@ -4604,10 +4604,10 @@ function dispatchReReviewForFix(fixDispatchItem, meta, config) {
4604
4604
 
4605
4605
  /**
4606
4606
  * W-mpg58wv3 — Soft preference picker for re-review agent assignment. Returns
4607
- * a list of agent IDs whose configured skills include any of
4607
+ * a list of agent IDs whose configured expertise tags include any of
4608
4608
  * REVIEW_SKILL_TAGS; the caller passes this to resolveAgent as `agentHints`,
4609
4609
  * which tries each in order before falling through to the default routing
4610
- * path. Empty list when no configured agent has a review skill — the standard
4610
+ * path. Empty list when no configured agent has a review tag — the standard
4611
4611
  * review route still wins, so no PR ever blocks waiting for a specific agent.
4612
4612
  */
4613
4613
  const REVIEW_SKILL_TAGS = ['code-review', 'design-review'];
@@ -4617,8 +4617,10 @@ function pickReReviewAgentHints(config, opts = {}) {
4617
4617
  const out = [];
4618
4618
  for (const [id, agent] of Object.entries(agents)) {
4619
4619
  if (excludeKey && String(id).toLowerCase() === excludeKey) continue;
4620
- const skills = Array.isArray(agent?.skills) ? agent.skills : [];
4621
- const lowered = skills.map(s => String(s || '').toLowerCase());
4620
+ // Read the descriptive expertise tags; tolerate legacy `skills` configs.
4621
+ const tags = Array.isArray(agent?.expertise) ? agent.expertise
4622
+ : (Array.isArray(agent?.skills) ? agent.skills : []);
4623
+ const lowered = tags.map(s => String(s || '').toLowerCase());
4622
4624
  if (REVIEW_SKILL_TAGS.some(tag => lowered.includes(tag))) out.push(id);
4623
4625
  }
4624
4626
  return out;
@@ -944,7 +944,7 @@ function renderPlaybook(type, vars) {
944
944
 
945
945
  // Lean system prompt: agent identity + rules only (~2-4KB, never grows)
946
946
  function buildSystemPrompt(agentId, config, project) {
947
- const agent = config.agents[agentId] || tempAgents.get(agentId) || { name: agentId, role: 'Temporary Agent', skills: [] };
947
+ const agent = config.agents[agentId] || tempAgents.get(agentId) || { name: agentId, role: 'Temporary Agent', expertise: [] };
948
948
  const charter = getAgentCharter(agentId); // returns '' for temp agents (no charter file)
949
949
  project = project || getProjects(config)[0] || {};
950
950
 
@@ -953,7 +953,7 @@ function buildSystemPrompt(agentId, config, project) {
953
953
  // Agent identity
954
954
  prompt += `# You are ${agent.name} (${agent.role})\n\n`;
955
955
  prompt += `Agent ID: ${agentId}\n`;
956
- prompt += `Expertise: ${(agent.skills || []).join(', ')}\n\n`;
956
+ prompt += `Expertise: ${(agent.expertise ?? agent.skills ?? []).join(', ')}\n\n`;
957
957
 
958
958
  // Charter (detailed instructions — typically 1-2KB)
959
959
  if (charter) {
package/engine/queries.js CHANGED
@@ -679,7 +679,7 @@ function getAgents(config) {
679
679
  const seen = new Set(roster.map(a => a.id));
680
680
  for (const d of (dispatch.active || [])) {
681
681
  if (d.agent && d.agent.startsWith('temp-') && !seen.has(d.agent)) {
682
- roster.push({ id: d.agent, name: d.agentName || d.agent, role: d.agentRole || 'Temp Agent', emoji: '\u{1F4A8}', skills: [], _temp: true });
682
+ roster.push({ id: d.agent, name: d.agentName || d.agent, role: d.agentRole || 'Temp Agent', emoji: '\u{1F4A8}', expertise: [], _temp: true });
683
683
  seen.add(d.agent);
684
684
  }
685
685
  }
@@ -722,6 +722,9 @@ function getAgents(config) {
722
722
  if (lastAction.length > 120) lastAction = lastAction.slice(0, 120) + '...';
723
723
  return {
724
724
  ...a, runtime, model, status: s.status, lastAction,
725
+ // Descriptive capability tags; tolerate legacy `skills` configs by
726
+ // normalizing to `expertise` for the dashboard/settings UI.
727
+ expertise: a.expertise ?? a.skills ?? [],
725
728
  currentTask: (s.task || '').slice(0, 200),
726
729
  // P-w2c8d1e7 — Phase 1.2: surface the active dispatch id so watch
727
730
  // captureState can snapshot it for currentDispatchId-aware predicates.
package/engine/shared.js CHANGED
@@ -4443,11 +4443,11 @@ const DEFAULT_AGENT_METRICS = {
4443
4443
  };
4444
4444
 
4445
4445
  const DEFAULT_AGENTS = {
4446
- ripley: { name: 'Ripley', emoji: '\u{1F3D7}\uFE0F', role: 'Lead / Explorer', skills: ['architecture', 'codebase-exploration', 'design-review'] },
4447
- dallas: { name: 'Dallas', emoji: '\u{1F527}', role: 'Engineer', skills: ['implementation', 'typescript', 'docker', 'testing'] },
4448
- lambert: { name: 'Lambert', emoji: '\u{1F4CA}', role: 'Analyst', skills: ['gap-analysis', 'requirements', 'documentation'] },
4449
- rebecca: { name: 'Rebecca', emoji: '\u{1F9E0}', role: 'Architect', skills: ['system-design', 'api-design', 'scalability', 'implementation'] },
4450
- ralph: { name: 'Ralph', emoji: '\u2699\uFE0F', role: 'Engineer', skills: ['implementation', 'bug-fixes', 'testing', 'scaffolding'] },
4446
+ ripley: { name: 'Ripley', emoji: '\u{1F3D7}\uFE0F', role: 'Lead / Explorer', expertise: ['architecture', 'codebase-exploration', 'design-review'] },
4447
+ dallas: { name: 'Dallas', emoji: '\u{1F527}', role: 'Engineer', expertise: ['implementation', 'typescript', 'docker', 'testing'] },
4448
+ lambert: { name: 'Lambert', emoji: '\u{1F4CA}', role: 'Analyst', expertise: ['gap-analysis', 'requirements', 'documentation'] },
4449
+ rebecca: { name: 'Rebecca', emoji: '\u{1F9E0}', role: 'Architect', expertise: ['system-design', 'api-design', 'scalability', 'implementation'] },
4450
+ ralph: { name: 'Ralph', emoji: '\u2699\uFE0F', role: 'Engineer', expertise: ['implementation', 'bug-fixes', 'testing', 'scaffolding'] },
4451
4451
  };
4452
4452
 
4453
4453
  const DEFAULT_CLAUDE = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2221",
3
+ "version": "0.1.2222",
4
4
  "description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
5
5
  "bin": {
6
6
  "minions": "bin/minions.js"