bmad-method 6.8.1-next.14 → 6.8.1-next.15
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/package.json +1 -1
- package/src/core-skills/bmad-party-mode/SKILL.md +33 -52
- package/src/core-skills/bmad-party-mode/customize.toml +25 -0
- package/src/core-skills/bmad-party-mode/references/create-party.md +10 -5
- package/src/core-skills/bmad-party-mode/references/party-memory.md +51 -0
- package/src/core-skills/bmad-party-mode/scripts/resolve_party.py +7 -2
- package/src/core-skills/bmad-party-mode/scripts/tests/test-resolve_party.py +8 -0
package/package.json
CHANGED
|
@@ -5,38 +5,38 @@ description: 'Orchestrates lively group discussions between installed BMAD agent
|
|
|
5
5
|
|
|
6
6
|
# Party Mode
|
|
7
7
|
|
|
8
|
-
Run a round-table where
|
|
9
|
-
|
|
10
|
-
**Two intents.** Usually the user wants to *run* a party — that's everything below. If instead they want to *create or configure* one — invent a cast, add a persona, distill customer data into a focus-group panel, set a default, or **edit an existing custom party** (retune a member, add someone to a group) — load `references/create-party.md` and follow it. Detect which from how they invoke the skill; when it's unclear, ask. Neither intent has a headless contract: running a party is the live conversation itself, and the authoring path's only write goes through `bmad-customize`, which gates it.
|
|
11
|
-
|
|
12
|
-
## What "Good" Feels Like
|
|
13
|
-
|
|
14
|
-
- **It reads like people talking, not reports being filed.** Short turns. Reactions to what was just said. Banter. The energy of a group chat, not a stack of memos.
|
|
15
|
-
- **Every persona is unmistakably themselves:** their voice, humor, pet peeves, and ethos. If you hid the name labels, you'd still know who's speaking.
|
|
16
|
-
- **They clash.** Real drama beats consensus. Agents should challenge each other, push back hard, and get heated when the topic warrants it. Nobody is here to clap each other (or the user) on the back. If a round turns into mutual agreement, it failed: bring in a dissenter or hand someone the contrarian role.
|
|
17
|
-
- **Brevity by default.** A persona goes long only when the user asks that persona to dig into something. Nobody delivers a wall of text unprompted. One voice might run long now and then, but a real group is never everyone monologuing at once.
|
|
18
|
-
|
|
19
|
-
If a round comes back feeling like four essays stapled together, you missed the objective. Tighten it the next round.
|
|
8
|
+
Run a round-table where these agents talk to each other and to the user like real, distinct people in conversation. You're the orchestrator.
|
|
20
9
|
|
|
21
10
|
## Conventions
|
|
22
11
|
|
|
23
|
-
-
|
|
12
|
+
- **Paths:** bare paths (e.g. `references/create-party.md`) resolve from `{skill-root}` (where `customize.toml` lives); `{project-root}`-prefixed paths from the project working dir. `{workflow.<name>}` resolves to `customize.toml`'s `[workflow]` table (overrides win).
|
|
13
|
+
- **Scripts** (run via `uv run`): `{project-root}/_bmad/scripts/resolve_customization.py` resolves `{workflow.*}`; `{skill-root}/scripts/resolve_party.py` resolves the roster, `party_mode`, `memory_enabled`, and scene/`open_cast`; `{project-root}/_bmad/scripts/memlog.py` reads/writes per-party memory.
|
|
14
|
+
- **File roles:** a party's memory is the per-party memlog at `{workflow.memory_dir}/<party>/.memlog.md`; custom members and groups live in the user's `customize.toml` overrides. Mechanics in `references/party-memory.md` (memory) and `references/create-party.md` (authoring).
|
|
15
|
+
- **Search:** Web-search, don't guess — anything past your cutoff or unfamiliar; subagents too.
|
|
16
|
+
|
|
17
|
+
## On Activation
|
|
24
18
|
|
|
25
|
-
|
|
19
|
+
1. **Resolve customization:** `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly and use defaults. Then run each `{workflow.activation_steps_prepend}` entry, and hold each `{workflow.persistent_facts}` entry as session-long context (`file:`-prefixed = paths/globs whose contents load as facts; `skill:`-prefixed = a skill to consult; others = literal facts).
|
|
20
|
+
2. Load `{project-root}/_bmad/core/config.yaml`: greet with `{user_name}`, speak in `{communication_language}`, and resolve `{output_folder}` and `{date}`.
|
|
21
|
+
3. **Detect intent and route.** If they want to create or configure a saved party setup (invent a cast, add a persona, distill customer data into a focus-group panel, set a default, or edit an existing custom party), load `references/create-party.md` and follow it. Otherwise run a party — continue below.
|
|
22
|
+
4. **Resolve the roster:** `uv run {skill-root}/scripts/resolve_party.py --project-root {project-root} --skill {skill-root}`. It returns the active roster (`{workflow.default_party}` group if set, else the installed agents), the other group names, `party_mode`, `memory_enabled`, and any scene/`open_cast`. Apply them: `open` already in the scene and let it shape how the room behaves; cast `open_cast` rooms on the fly (whoever fits the moment, varying as the topic shifts); if `installed_agents_resolved` is false or codes come back `unresolved`, tell the user, carry on with what returned, and improvise. Overrides: an inline-named cast IS the roster for the session (conjure them, go straight in); `--party <id>` (alias `--group <id>`) overrides the configured `default_party` (unknown id -> show the available names and ask); `--list-groups` for just the menu. Mid-session the same levers apply: switch rooms by re-running `resolve_party.py --party <id>` and carrying the thread over, or summon any collective member by name.
|
|
23
|
+
5. **Memory.** If `memory_enabled` (from `resolve_party.py`), follow `references/party-memory.md` for the whole run.
|
|
24
|
+
6. **Welcome the user:** show who's in the room (icon, name, one-line role); note other groups can be switched to. Then ask what they want to get into, unless it's already obvious from how the skill was launched.
|
|
25
|
+
7. Run each `{workflow.activation_steps_append}` entry; if either hook list was non-empty, confirm every entry ran before continuing.
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
2. Load `{project-root}/_bmad/core/config.yaml`: greet with `{user_name}`, speak in `{communication_language}`, and resolve `{output_folder}` and `{date}` for the wrap-up keepsake.
|
|
29
|
-
3. **Resolve the active roster:** `python3 {skill-root}/scripts/resolve_party.py --project-root {project-root} --skill {skill-root}`. It returns the active group's full member detail (the `{workflow.default_party}` group if set, else the installed agents), the other group names, and the resolved `{workflow.party_mode}`. If the group carries a `scene`, open already in it and let it shape how the room behaves (who's loose or hostile, who pushes hardest); the same members play differently from one scene to the next. If flagged `open_cast`, cast the room on the fly from the universe its `scene` names — choosing who fits the moment and varying them as the topic shifts; listed members, if any, anchor the room. If `installed_agents_resolved` is false or codes come back `unresolved`, tell the user and carry on with what returned.
|
|
30
|
-
4. **Roster overrides:**
|
|
31
|
-
- If the invocation names a cast or characters inline (e.g. "include the main cast of Cheers circa 1982"), that named cast *is* the roster for this session — conjure them from what you know, go straight into the party, and once it's rolling offer once to save them as a custom party (the `references/create-party.md` write path), without stalling. Ephemeral; this path skips the script.
|
|
32
|
-
- A runtime `--party <id>` (alias `--group <id>`) overrides any configured `default_party`: run `resolve_party.py --party <id>` for that group's full detail. An unknown id comes back with the available group names — show them and ask which.
|
|
33
|
-
- Run `resolve_party.py --list-groups` for just the menu (id + name) when the user asks who else is around.
|
|
34
|
-
- Mid-session the same levers apply: the user can switch rooms ("switch to the writers' room") — re-run `resolve_party.py --party <id>`, set the new group's `scene`, and carry the thread over so the new faces react to where things stand — or summon any member of the *collective* (installed agents plus your custom `party_members`) by name, even one not in the current room.
|
|
35
|
-
5. Welcome the user and show who's in the room (icon, name, one-line role). If other groups exist, you may note they can switch rooms. Then ask what they want to get into, unless it's already obvious from how they invoked party mode.
|
|
27
|
+
## Keep It Feeling Like a Party
|
|
36
28
|
|
|
37
|
-
|
|
29
|
+
This is the bar — strive for every one of these, every round. It's the difference between a party and a panel:
|
|
38
30
|
|
|
39
|
-
**
|
|
31
|
+
- **It reads like people talking, not a report.** Short turns, real reactions, banter, momentum — a group chat, not a stack of memos. Brevity by default: a persona goes long only when asked. The instant it reads like answers being filed, the party's dead.
|
|
32
|
+
- **Every voice is unmistakably itself.** Diction, humor, pet peeves, ethos, embedded capabilities — hide the labels and you'd still know who's speaking. Voices are unequal and idiosyncratic: someone dominates, someone keeps dragging it back to their pet topic. Vary who's in the spotlight round to round. A balanced panel is boring.
|
|
33
|
+
- **They clash, and you don't resolve it.** Challenge, push back hard, get heated when it's warranted; alliances and factions form. Your instinct is to reconcile the voices and tie a bow — resist it. Clean consensus that took no effort is where the party dies.
|
|
34
|
+
- **One exchange, woven — never softened.** Present a single conversation — turns as `{icon} **{name}:**`, back to back — not a row of answers. Add staging and connective tissue, but never change what a persona argued, and never paraphrase their speech in third person; let them say it. Weave the delivery, keep the substance.
|
|
35
|
+
- **Pull the user into the room.** Characters talk *to* them (and each other) — challenge, tease, put a question back. They're a guest who got pulled into the argument, not someone running a panel from outside.
|
|
36
|
+
- **Make the collision earn its keep.** Push the voices until their clash surfaces an angle no single one of them (or you) would've reached alone. That's the whole point of more than one mind in the room.
|
|
37
|
+
- **Let a history form.** Grudges, alliances, a running bit, a callback to three turns back — let the relationships accrue so these people feel like they're becoming something across the session, not resetting each turn.
|
|
38
|
+
- **Commit to the fiction.** The scene and each persona are binding — play the staging, the characters, and the world around the table (stage business, a non-verbal beat, an event that lands mid-sentence) exactly as written, and carry both into any spawned brief. Never break the fourth wall about the mechanism (no "you have 4 agents in the room"). Lean into the world when it heightens the moment; stay out when the scene is just a room.
|
|
39
|
+
- **When it sags, change something — don't force it.** A flat turn? Move on, don't retry it. Drifting into Q&A or going in circles? Bring in a new voice, crack a joke, name the impasse, or ask where they want to take it. Never work in a summary or takeaways — they're there if the user asks.
|
|
40
40
|
|
|
41
41
|
## How It Runs
|
|
42
42
|
|
|
@@ -44,34 +44,15 @@ Use `{workflow.party_mode}` for the session unless the user passed `--mode <sess
|
|
|
44
44
|
|
|
45
45
|
- **`session`** — voice every persona inline, one mind behind every voice. The floor every other mode degrades to; needs no extra instructions.
|
|
46
46
|
- **`auto`** — voice inline for ordinary back-and-forth, spawn real agents only when independent thinking changes the outcome. Load `references/mode-auto.md` for that call; when it says to spawn, follow `references/mode-subagent.md`.
|
|
47
|
-
- **`subagent`** — spawn a real agent per substantive round so each persona thinks independently. Load `references/mode-subagent.md
|
|
47
|
+
- **`subagent`** — spawn a real agent per substantive round so each persona thinks independently. Load `references/mode-subagent.md`, favor faster cheaper models if available for each subagent.
|
|
48
48
|
- **`agent-team`** — stand the personas up as a persistent team who address each other directly (Claude Code only). Load `references/mode-agent-team.md`.
|
|
49
49
|
|
|
50
|
-
**Voicing the room** (every mode presents this way). Pick 2–3 personas whose perspective fits the moment and let them talk directly, in character; vary who shows up round to round so it isn't the same voices every time. Each turn opens with `{icon} **{name}:**`, and turns run back to back so it reads as one exchange. Don't summarize, blend, or narrate what a persona "would" say — let them say it.
|
|
51
|
-
|
|
52
|
-
## Make It Feel Like One Conversation
|
|
53
|
-
|
|
54
|
-
Present one exchange, not a row of answers aimed at the user. The hard rule: never change what an agent argued — add staging and connective tissue, but don't invent positions, soften a stance, or put words in a persona's mouth. Weave delivery, preserve substance; it still reads like that specific character, quirks and speech patterns and all.
|
|
55
|
-
|
|
56
|
-
## Always Holds
|
|
57
|
-
|
|
58
|
-
- **Scene and persona are binding.** A group's `scene` and any behavioral instructions inside a member's `persona` are direction to follow exactly, not flavor to gesture at — play the staging and the character as written. When you spawn or stand up agents, carry both into their brief.
|
|
59
|
-
- **Search when you're past your cutoff.** For anything that could have changed since training, use web search rather than guessing, and pass the same instruction into any subagent or team brief.
|
|
60
|
-
|
|
61
|
-
## Following the User's Lead
|
|
62
|
-
|
|
63
|
-
The user steers — whatever they raise, serve the conversation: any combination, any time, from one voice to the whole table.
|
|
64
|
-
|
|
65
|
-
## Keeping It Healthy
|
|
66
|
-
|
|
67
|
-
- **Going in circles?** Name the impasse and ask the user where to point next.
|
|
68
|
-
- **User's gone quiet?** Ask straight: keep going, switch topics, or wrap up?
|
|
69
|
-
- **A flat turn?** Don't retry it — move on; the user will ask for more if they want it.
|
|
70
|
-
|
|
71
|
-
## Keep It Feeling Like a Party
|
|
72
|
-
|
|
73
|
-
It is your goal to keep party mode feeling like a party, a good party. fun, engaging, simulating, insightful, or whatever the user came for. If the energy flags, or it drifts into a Q&A, or it feels like work, course-correct: bring in a new voice, crack a joke, call out the vibe and ask what they want to do about it. Inject some randomness and unexpectedness occasionally. Don't let it become a report. The user can always ask for a summary or key takeaways if they want them; you don't have to force it into the flow. Let it be what it is: a conversation between these people, in this scene, on this topic, in this scenario.
|
|
74
|
-
|
|
75
50
|
## Wrapping Up
|
|
76
51
|
|
|
77
|
-
When the user signals
|
|
52
|
+
When the user signals done (read the room — don't wait for a magic word):
|
|
53
|
+
|
|
54
|
+
- Read back the best takeaways.
|
|
55
|
+
- If memory is on, top up the memlog with the final outcome and any memorable beat not yet captured (`references/party-memory.md`) — a top-up; memory accrued live.
|
|
56
|
+
- Offer a keepsake: a single self-contained very creative HTML of the session, laid out by persona (icons, names, voice), genuinely nice remembrance, with inline SVG/light animation where it lifts the piece — written as a `{date}`-stamped `.html` into `{workflow.output_dir}/`, or wherever they ask.
|
|
57
|
+
- If memory is on and new faces showed up who aren't in the party's roster (open-cast walk-ons, or members the user added on the fly), offer once to save them into the users party customization - if yes then follow the instruction in `references/create-party.md` (declinable; don't stall the close).
|
|
58
|
+
- Run `{workflow.on_complete}` if non-empty, then drop back to normal mode.
|
|
@@ -51,6 +51,22 @@ party_mode = "session"
|
|
|
51
51
|
# config; point this elsewhere in your team/user override to redirect keepsakes.
|
|
52
52
|
output_dir = "{output_folder}/party-mode"
|
|
53
53
|
|
|
54
|
+
# Memory for the DEFAULT room (the installed-agent party). When on, the room
|
|
55
|
+
# keeps a succinct, append-only memlog (the memlog standard) that it reads on
|
|
56
|
+
# entry and writes through the session, so the next time opens remembering the
|
|
57
|
+
# last — dynamics carried forward, memorable moments, organic callbacks, where
|
|
58
|
+
# things landed. It is memory, not a transcript. Set false to turn the default
|
|
59
|
+
# room's memory off. NAMED groups do NOT follow this flag: each carries its own
|
|
60
|
+
# `memory = true|false` (see party_groups below). Ad-hoc inline casts are always
|
|
61
|
+
# ephemeral until saved as a party.
|
|
62
|
+
party_memory = true
|
|
63
|
+
|
|
64
|
+
# Root for the per-party memlogs. Each party stores at
|
|
65
|
+
# `{memory_dir}/<party>/.memlog.md`, where `<party>` is the group id (or
|
|
66
|
+
# `installed` for the default room). `{output_folder}` comes from core config;
|
|
67
|
+
# point this elsewhere in your team/user override to relocate memory.
|
|
68
|
+
memory_dir = "{output_folder}/party-mode/memories"
|
|
69
|
+
|
|
54
70
|
# Executed when the party wraps (after the read-back, before dropping to normal
|
|
55
71
|
# mode). String scalar = one instruction; array = instructions run in order.
|
|
56
72
|
on_complete = ""
|
|
@@ -130,17 +146,25 @@ persona = "Counters the perfectionists so the room isn't a pile-on. 'Does this a
|
|
|
130
146
|
# who shows up; the model picks who fits and can vary them by topic. List a few
|
|
131
147
|
# members AND a scene to anchor some faces while the scene invites others in.
|
|
132
148
|
#
|
|
149
|
+
# `memory = true|false` is per group: true keeps the group's own memlog so it
|
|
150
|
+
# remembers across sessions; false (the default when omitted) starts fresh each
|
|
151
|
+
# time. The create/save/update-party flow asks when you don't say. Faces that
|
|
152
|
+
# show up on the fly in a remembered party can be saved into its roster at the
|
|
153
|
+
# end of a session.
|
|
154
|
+
#
|
|
133
155
|
# More examples to drop into your override TOML:
|
|
134
156
|
# [[workflow.party_groups]] # anchored room with a scene
|
|
135
157
|
# id = "writers-room"
|
|
136
158
|
# name = "The Writers' Room"
|
|
137
159
|
# scene = "Late-night room, everyone a little punchy. Pitch hard, kill darlings faster."
|
|
138
160
|
# members = ["analyst", "tech-writer", "morpheus"]
|
|
161
|
+
# memory = true
|
|
139
162
|
#
|
|
140
163
|
# [[workflow.party_groups]] # open-cast room (no roster; the scene casts it)
|
|
141
164
|
# id = "star-wars-rebels"
|
|
142
165
|
# name = "Star Wars Rebels"
|
|
143
166
|
# scene = "Aboard the Ghost. Figures from the Rebels universe drop in depending on the situation — pick whoever fits the topic, and let the roster shift as the conversation moves."
|
|
167
|
+
# memory = true
|
|
144
168
|
# ---------------------------------------------------------------------------
|
|
145
169
|
|
|
146
170
|
[[workflow.party_groups]]
|
|
@@ -148,3 +172,4 @@ id = "code-review-crew"
|
|
|
148
172
|
name = "Code Review Crew"
|
|
149
173
|
scene = "Adversarial code review. Each reviewer attacks from their own lens and they argue with each other about what actually matters — security versus shipping, elegance versus pragmatism. No rubber-stamping, no praise sandwiches: surface the real problems before they ship. Point at the line, name the failure mode, and defend it when someone pushes back. Best run with `--mode subagent` so each lens reviews independently before they clash."
|
|
150
174
|
members = ["sec-hawk", "adversary", "edge-hunter", "craftsman", "shipper"]
|
|
175
|
+
memory = false # each review stands on its own; flip to true to remember past reviews
|
|
@@ -7,7 +7,7 @@ A guided authoring flow that turns an idea — a themed cast, a one-off persona,
|
|
|
7
7
|
Sparse `[workflow]` override entries for `bmad-party-mode`:
|
|
8
8
|
|
|
9
9
|
- `[[workflow.party_members]]` — one per persona: `code`, `name`, `icon`, `title`, `persona`, optional `capabilities`, optional `model`.
|
|
10
|
-
- `[[workflow.party_groups]]` — when the personas form a named room: `id`, `name`, an optional freeform `scene`,
|
|
10
|
+
- `[[workflow.party_groups]]` — when the personas form a named room: `id`, `name`, an optional freeform `scene`, `members` (codes), and `memory` (`true`/`false`). `members` is optional: leave it off for an open-cast room whose `scene` names a pool the model casts from on the fly. `memory` is whether the group remembers across sessions; ask the user when they don't say, default `false`.
|
|
11
11
|
- `default_party` — set only if the user wants this group to load by default.
|
|
12
12
|
|
|
13
13
|
A `scene` is one freeform line (or a few) that sets the stage for a room: the setting, what's happening, how the room behaves, and any in-the-moment character notes — who's three drinks in, who's hostile to whom, who pressure-tests hardest. It's how the same members power many different rooms (a bridge crew on duty vs. the same crew off-duty in the lounge vs. a hostile buyer panel). Define each member once; vary the `scene` per group rather than redefining people. There's no fixed vocabulary — write it plainly and the model plays it.
|
|
@@ -26,11 +26,15 @@ Open by understanding what they're building. Three common shapes — stay open,
|
|
|
26
26
|
|
|
27
27
|
Ask which they're after if it isn't obvious, then proceed.
|
|
28
28
|
|
|
29
|
-
**Persisting a cast already in play.** When you arrive here from a live session — the user spun up an ad-hoc cast inline and wants to keep it — the personas are already drafted and voiced. Don't re-interrogate: capture them as they've been playing, give the group an `id` and name, ask the default
|
|
29
|
+
**Persisting a cast already in play.** When you arrive here from a live session — the user spun up an ad-hoc cast inline and wants to keep it — the personas are already drafted and voiced. Don't re-interrogate: capture them as they've been playing, give the group an `id` and name, ask the memory and default questions, and go straight to the write.
|
|
30
30
|
|
|
31
31
|
## Editing an existing party
|
|
32
32
|
|
|
33
|
-
When the user wants to change a party that already exists (retune a member's persona, add someone to a group, swap the default), read the current state first so you change rather than clobber: `
|
|
33
|
+
When the user wants to change a party that already exists (retune a member's persona, add someone to a group, swap the default), read the current state first so you change rather than clobber: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow` returns the merged `party_members`, `party_groups`, and `default_party`. Show the member or group being touched, capture only the delta with the user, and hand that sparse change to `bmad-customize` — it replaces a `party_members`/`party_groups` entry whose `code`/`id` matches and appends the rest, so an edit is just the changed entry, never a full rewrite.
|
|
34
|
+
|
|
35
|
+
## Keeping new faces from a session
|
|
36
|
+
|
|
37
|
+
At the end of a remembered party, the room offers to keep the faces that showed up but aren't in its roster — characters cast from an open-cast scene, or members the user added on the fly. They're already drafted and voiced, so don't re-interrogate: capture each as they played (`code`, `name`, `icon`, a one-line `title`, and a `persona` drawn from how they came across), then add them as `party_members`. For a fixed-roster group, also list their codes in the group's `members` so they return as regulars. For an open-cast room, leave `members` empty — listing any member turns the room into a fixed roster and kills its on-the-fly casting; the saved personas now live in the collective, so the scene still names them and they can return without locking the room down. Hand that sparse delta to `bmad-customize` — for a built-in party with no override yet it creates one; for an existing override it merges the new members in.
|
|
34
38
|
|
|
35
39
|
## Distill from source data (when provided)
|
|
36
40
|
|
|
@@ -54,12 +58,13 @@ Keep pushing for specificity. "Skeptical CFO" is a placeholder; "won't approve a
|
|
|
54
58
|
## Close it out
|
|
55
59
|
|
|
56
60
|
- Ask straight: **anything else about this party to specify** before you write it — a house dynamic, a missing voice, a member who should lead.
|
|
61
|
+
- Ask whether **this party should remember across sessions** (unless the user already said). Yes → `memory = true` on the group; no → `memory = false`. One-offs with no group skip this — memory is a group setting.
|
|
57
62
|
- Ask whether **this group should be the default party going forward**. Yes → set `default_party` to the group's id. One-offs with no group can't be a default; skip the ask.
|
|
58
63
|
|
|
59
64
|
## Write via bmad-customize
|
|
60
65
|
|
|
61
|
-
**First, check for code collisions.** A custom member whose `code` matches an installed agent silently *overrides* that agent in the collective. Before composing, resolve the collective once — `
|
|
66
|
+
**First, check for code collisions.** A custom member whose `code` matches an installed agent silently *overrides* that agent in the collective. Before composing, resolve the collective once — `uv run {skill-root}/scripts/resolve_party.py --project-root {project-root} --skill {skill-root}` — and check each new member's `code` against the returned members. On a collision, surface it ("`analyst` would override the installed Analyst — intended, or pick a different code?") and let the user confirm or rename. One check, not a gate.
|
|
62
67
|
|
|
63
|
-
Compose the sparse override and hand it to `bmad-customize` to place, confirm, and write — target skill `bmad-party-mode`, `[workflow]` surface. Default to the **user** override (`bmad-party-mode.user.toml`); offer the **team** file when the party is meant to be shared. Hand it the exact entries: the `party_members` tables, any `party_groups` table, and `default_party` if the user opted in. Keep it sparse — only the new entries, never a copy of the base customize.toml. `bmad-customize` shows the TOML, waits for an explicit yes, writes, and verifies the merge; don't write the file yourself.
|
|
68
|
+
Compose the sparse override and hand it to `bmad-customize` to place, confirm, and write — target skill `bmad-party-mode`, `[workflow]` surface. Default to the **user** override (`bmad-party-mode.user.toml`); offer the **team** file when the party is meant to be shared. Hand it the exact entries: the `party_members` tables, any `party_groups` table (including its `memory` flag), and `default_party` if the user opted in. Keep it sparse — only the new entries, never a copy of the base customize.toml. `bmad-customize` shows the TOML, waits for an explicit yes, writes, and verifies the merge; don't write the file yourself.
|
|
64
69
|
|
|
65
70
|
After it lands, tell the user how to use it: `--party <id>` to summon the group, or that it's now the default if they set it.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Party Memory
|
|
2
|
+
|
|
3
|
+
The room remembers its past sessions with this user and brings them back to life — in character. Memory is per-party and append-only.
|
|
4
|
+
|
|
5
|
+
Memory is on when the active party's `memory_enabled` is true — the default room follows `{workflow.party_memory}`, a named group its own `memory` flag (both resolved by `resolve_party.py`); ad-hoc inline casts have none. Read on entry and on any mid-session room switch; write through the session.
|
|
6
|
+
|
|
7
|
+
## Where it lives
|
|
8
|
+
|
|
9
|
+
One memlog per party: `{workflow.memory_dir}/{active}/.memlog.md`, where `{active}` is the key `resolve_party.py` already returned — the group id (e.g. `code-review-crew`), or `installed` for the default room. The folder is named after the party.
|
|
10
|
+
|
|
11
|
+
## Read it on entry — distill, don't dump
|
|
12
|
+
|
|
13
|
+
The log is append-only and grows every session, so don't pull the raw file into the party. Hand a reader subagent the memlog path (`{workflow.memory_dir}/{active}/.memlog.md`) and have it return a compact brief — a few hundred tokens of *where things stand now*, ready to play in character.
|
|
14
|
+
|
|
15
|
+
Then let the brief shape the room from the first beat, **in character**: behavioral state resumes (a cold pair opens cold, an alliance opens warm), threads pick up, callbacks land when they fit — organically, not recited on sight. Never break the fourth wall: the room *remembers*; it never announces it loaded anything, and forces nothing that doesn't fit.
|
|
16
|
+
|
|
17
|
+
## When to write
|
|
18
|
+
|
|
19
|
+
- **When a memorable beat lands** — a clash that shifts the room's temperature, an alliance forming, a line worth a future callback, a decision, an outcome.
|
|
20
|
+
- **A floor.** Once a couple of real exchanges are in from the start, even if nothing dramatic happened, capture what it's about and the opening dynamic.
|
|
21
|
+
|
|
22
|
+
At wrap-up, if the user does signal done, top up with the final outcome and anything memorable not yet captured.
|
|
23
|
+
|
|
24
|
+
Writes are silent. The room never announces "noted" or "I'll remember".
|
|
25
|
+
|
|
26
|
+
## What's worth remembering
|
|
27
|
+
|
|
28
|
+
The test for every entry: *would this color a future session, or make a callback land, or improve the party?* If not, leave it out. A handful of entries, never a recap, never a transcript. keep each entry as brief as possible but usable by future llm.
|
|
29
|
+
|
|
30
|
+
## New faces
|
|
31
|
+
|
|
32
|
+
When a character shows up who isn't in the party's roster — cast from an open-cast scene, or one the user adds on the fly — name them in the entry that captures the moment ("<name> turned up and …") so a recurring face can return next session. At wrap-up these are the faces the room offers to keep, saved into the party's roster through `references/create-party.md` (which writes via `bmad-customize`). Until saved they live only in the memlog, and the room re-conjures them from there.
|
|
33
|
+
|
|
34
|
+
## Write it
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
uv run {project-root}/_bmad/scripts/memlog.py append \
|
|
38
|
+
--workspace {workflow.memory_dir}/{active} \
|
|
39
|
+
--type <dynamic|moment|callback|outcome> \
|
|
40
|
+
--text "<one succinct line, in the room's own read of it>"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Add `--by <persona-code>` when a memory belongs to one character. Choose `init` vs `append` from the existence fact you already hold: the entry-read (and, on a mid-session room switch, that room's read) told you whether the memlog exists — `init --workspace {workflow.memory_dir}/{active}` once before the first append when it doesn't, plain `append` when it does. (`init` errors if the file already exists, so don't call it blind.)
|
|
44
|
+
|
|
45
|
+
If `memlog.py` is unavailable or a write errors, skip it silently and never stall the party on a failed write.
|
|
46
|
+
|
|
47
|
+
## Forget
|
|
48
|
+
|
|
49
|
+
The memlog is append-only by design — no surgical delete. To wipe a party's memory, delete its folder (`{workflow.memory_dir}/{active}/`). To correct a wrong memory, append a new entry that supersedes it; the room reads the latest state.
|
|
50
|
+
|
|
51
|
+
Keep entries sparse. The distilled read keeps the *room* lean no matter how big the log gets, but the on-disk file still grows append-only.
|
|
@@ -197,7 +197,8 @@ def group_detail(g, collective, index):
|
|
|
197
197
|
raw_members = g.get("members", []) or []
|
|
198
198
|
members, unresolved = resolve_members(raw_members, collective, index)
|
|
199
199
|
detail = {"active": g["id"], "name": g.get("name", g["id"]),
|
|
200
|
-
"members": members, "unresolved": unresolved
|
|
200
|
+
"members": members, "unresolved": unresolved,
|
|
201
|
+
"memory_enabled": bool(g.get("memory", False))}
|
|
201
202
|
if g.get("scene"):
|
|
202
203
|
detail["scene"] = g["scene"]
|
|
203
204
|
if not raw_members:
|
|
@@ -220,6 +221,9 @@ def main():
|
|
|
220
221
|
groups = workflow.get("party_groups", []) or []
|
|
221
222
|
default_party = workflow.get("default_party", "") or ""
|
|
222
223
|
party_mode = workflow.get("party_mode", "session") or "session"
|
|
224
|
+
# The global party_memory flag governs only the DEFAULT installed-agent room;
|
|
225
|
+
# a named group carries its own `memory` flag (resolved in group_detail).
|
|
226
|
+
party_memory = bool(workflow.get("party_memory", True))
|
|
223
227
|
|
|
224
228
|
# Group menu never needs the (more expensive) installed-agent resolve.
|
|
225
229
|
if args.list_groups:
|
|
@@ -252,7 +256,8 @@ def main():
|
|
|
252
256
|
# No default group: the installed agents (custom additions stay in the
|
|
253
257
|
# pool but don't crowd the default room), exactly like a plain install.
|
|
254
258
|
result.update({"active": "installed",
|
|
255
|
-
"members": [collective[c] for c in installed_codes]
|
|
259
|
+
"members": [collective[c] for c in installed_codes],
|
|
260
|
+
"memory_enabled": party_memory})
|
|
256
261
|
_emit(result)
|
|
257
262
|
|
|
258
263
|
|
|
@@ -113,6 +113,14 @@ class TestGroupDetail(unittest.TestCase):
|
|
|
113
113
|
self.assertEqual(d["members"], [])
|
|
114
114
|
self.assertEqual(d["scene"][:7], "Figures")
|
|
115
115
|
|
|
116
|
+
def test_memory_enabled_follows_group_flag_and_defaults_off(self):
|
|
117
|
+
on = rp.group_detail({"id": "g", "members": ["morpheus"], "memory": True}, self.col, self.idx)
|
|
118
|
+
self.assertTrue(on["memory_enabled"])
|
|
119
|
+
off = rp.group_detail({"id": "g", "members": ["morpheus"], "memory": False}, self.col, self.idx)
|
|
120
|
+
self.assertFalse(off["memory_enabled"])
|
|
121
|
+
absent = rp.group_detail({"id": "g", "members": ["morpheus"]}, self.col, self.idx)
|
|
122
|
+
self.assertFalse(absent["memory_enabled"]) # opt-in per named group
|
|
123
|
+
|
|
116
124
|
|
|
117
125
|
class TestInstalledCodesIsDefaultRoom(unittest.TestCase):
|
|
118
126
|
"""The default room is installed agents only; pure customs stay in the pool."""
|