@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.
- package/dashboard/js/refresh.js +1 -1
- package/dashboard/js/render-prd.js +12 -2
- package/dashboard/js/settings.js +1 -1
- package/dashboard.js +9 -1
- package/docs/README.md +3 -1
- package/docs/deprecated.json +13 -0
- package/docs/named-agents.md +216 -0
- package/docs/specs/agent-configurability.md +268 -127
- package/docs/specs/agent-rename.md +163 -0
- package/engine/lifecycle.js +6 -4
- package/engine/playbook.js +2 -2
- package/engine/queries.js +4 -1
- package/engine/shared.js +5 -5
- package/package.json +1 -1
package/dashboard/js/refresh.js
CHANGED
|
@@ -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 =
|
|
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 })
|
package/dashboard/js/settings.js
CHANGED
|
@@ -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="
|
|
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
|
-
|
|
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-
|
|
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.
|
package/docs/deprecated.json
CHANGED
|
@@ -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
|
|
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 (
|
|
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
|
|
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
|
-
- **
|
|
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:
|
|
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
|
|
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
|
|
67
|
-
|
|
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
|
-
|
|
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: {
|
|
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
|
-
|
|
|
105
|
-
|
|
|
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
|
-
|
|
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 §
|
|
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",
|
|
120
|
-
"
|
|
121
|
-
"
|
|
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
|
|
146
|
+
"builtin": true // seeded from DEFAULT_ROLES — editable but NEVER deletable (§3.2)
|
|
125
147
|
},
|
|
126
|
-
"architect": { "label": "Architect", "
|
|
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",
|
|
131
|
-
"
|
|
132
|
-
"
|
|
133
|
-
"
|
|
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 **
|
|
143
|
-
- Effective **
|
|
144
|
-
- Effective **label** shown in `# You are {name} ({label})` = `roles[agent.role].label` ?? raw `agent.role` string (legacy).
|
|
145
|
-
- Effective **cli/model** chain
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
###
|
|
277
|
+
### 5.2 Data model (Phase 1)
|
|
161
278
|
|
|
162
|
-
- Add `DEFAULT_ROLES` to `engine/shared.js`,
|
|
163
|
-
- Migrate `agent.role` free-text → role key on first load (in-memory, mirroring `applyLegacyCcModelMigration`'s no-disk-rewrite pattern
|
|
164
|
-
-
|
|
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
|
-
###
|
|
283
|
+
### 5.3 Expertise display
|
|
167
284
|
|
|
168
|
-
|
|
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
|
-
###
|
|
287
|
+
### 5.4 Agent rename — *delivered by the prerequisite; surfaced in the Library*
|
|
171
288
|
|
|
172
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
333
|
+
Flag-gated like Phase 1.
|
|
334
|
+
|
|
335
|
+
### 7.1 Scope
|
|
205
336
|
|
|
206
|
-
- **Role CRUD:** create/rename/delete roles
|
|
207
|
-
- **Agent CRUD:** create a new named agent (id + name + emoji + role + optional overrides) and delete a
|
|
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
|
-
###
|
|
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
|
|
214
|
-
- A new agent with no `charter.md`
|
|
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
|
-
###
|
|
347
|
+
### 7.3 API (Phase 2)
|
|
217
348
|
|
|
218
|
-
- `POST /api/roles/create` `{ key, label,
|
|
219
|
-
- `POST /api/roles/delete` `{ key, reassignTo? }` — refuses if agents reference it and no `reassignTo` given
|
|
220
|
-
- `POST /api/agents/create` `{ id, name, emoji, role, overrides? }`.
|
|
221
|
-
- `POST /api/agents/delete` `{ id }` — refuses
|
|
222
|
-
- Extend `POST /api/settings/reset`
|
|
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
|
-
###
|
|
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
|
-
###
|
|
361
|
+
### 7.5 Phase 2 acceptance criteria
|
|
231
362
|
|
|
232
|
-
1. A user can create a role with a
|
|
233
|
-
2. A user can create a named agent bound to a role;
|
|
234
|
-
3. Deleting a role in use is blocked or forces reassignment
|
|
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;
|
|
368
|
+
6. `npm test` + `npm run lint` green; CRUD covered by unit tests; concurrency via `mutateJsonFileLocked`.
|
|
238
369
|
|
|
239
|
-
##
|
|
370
|
+
## 8. Out of scope (explicitly deferred)
|
|
240
371
|
|
|
241
|
-
- **
|
|
242
|
-
- **
|
|
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
|
|
377
|
+
- Any change to the meeting/plan agent-charter linkage beyond reading the new role-charter source.
|
|
246
378
|
|
|
247
|
-
##
|
|
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** —
|
|
251
|
-
3. **Charter
|
|
252
|
-
4. **
|
|
253
|
-
5. **Default role
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
-
|
|
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 enrichment (§5.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._
|
package/engine/lifecycle.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
4621
|
-
const
|
|
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;
|
package/engine/playbook.js
CHANGED
|
@@ -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',
|
|
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
|
|
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}',
|
|
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',
|
|
4447
|
-
dallas: { name: 'Dallas', emoji: '\u{1F527}', role: 'Engineer',
|
|
4448
|
-
lambert: { name: 'Lambert', emoji: '\u{1F4CA}', role: 'Analyst',
|
|
4449
|
-
rebecca: { name: 'Rebecca', emoji: '\u{1F9E0}', role: 'Architect',
|
|
4450
|
-
ralph: { name: 'Ralph', emoji: '\u2699\uFE0F', role: 'Engineer',
|
|
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.
|
|
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"
|