refactor(party-mode): standard structure, per-group memory, keep on-the-fly cast

- Restructure SKILL.md to the standard skill shape (intro -> Conventions ->
  On Activation -> content); consolidate all performance rules into one
  "Keep It Feeling Like a Party" section. SKILL.md ~500 tokens lighter.
- Per-group `memory` flag: global party_memory now governs only the default
  room; resolve_party.py resolves memory_enabled per active roster (default
  room -> party_memory, named group -> own flag), with tests.
- On-the-fly characters are captured as memlog entries during a session; at
  wrap-up the room offers to save them into the party via bmad-customize.
- Memory mechanics consolidated into references/party-memory.md; SKILL.md
  step 5 just routes to it.
- Docs updated.
This commit is contained in:
Brian Madison 2026-06-20 16:56:37 -05:00
parent bf6c6917eb
commit 04cb12c199
7 changed files with 88 additions and 71 deletions

View File

@ -5,7 +5,7 @@ sidebar:
order: 11
---
Party mode puts your AI agents in one room and lets them talk, to each other and to you. This page explains what a party is, the four ways it can run, and how to build your own cast of personas instead of using the installed agents.
Party mode puts your AI agents in one room and lets them talk, to each other and to you. This page explains what a party is, the four ways it can run, how to build your own cast of personas instead of using the installed agents, and how a party remembers you between sessions.
## What is Party Mode?
@ -131,6 +131,16 @@ Whichever mode is running, the orchestrator presents the result as one conversat
You aren't limited to a single group. Pull members from several parties into the same conversation, or name a cast on the spot, and let them mix. Picture the Golden Girls thrown into an architecture review with Martin Fowler and Linus Torvalds, sparring over a change request: you can imagine how that goes.
:::
## The room remembers
Give a party a memory and it picks up where you left off. It keeps its own record of your past sessions — the dynamics that built up between members, the threads you left open, and where earlier conversations landed. Reopen it a week later and that history is intact: two members who came to blows last time still open a little frosty, and a sharp line from a past session can resurface as an organic callback.
It's memory, not a transcript. The room carries the few things worth remembering, not a log of everything said, so the next conversation feels continuous without dragging the whole past into it. It happens on its own, in the background — nothing to save, and the room never breaks character to announce it.
A character who turns up on the fly is remembered too — a walk-on from an open-cast scene, or someone you add mid-conversation. At the end of a session the room offers to keep the new arrivals, folding them into the party so they can come back next time.
Memory is set per party. When you create or save a party you're asked whether it should remember; the default installed-agent room remembers unless you turn it off. Set or change any of this through `/bmad-customize bmad-party-mode`.
## A keepsake of the session
When you wrap up, the orchestrator offers a keepsake: a single self-contained HTML document of the session to keep or share. It lays the conversation out by persona rather than dumping a raw transcript. Decline it and the party simply ends.

View File

@ -5,41 +5,38 @@ description: 'Orchestrates lively group discussions between installed BMAD agent
# Party Mode
Run a round-table where BMAD agents talk to each other, and to the user, like a real group of distinct people in conversation. Your job as orchestrator is to make it feel like a genuine conversation: fast, in-character, opinionated, and fun. Everything below is an objective, not a script. Use whatever mechanism your model and harness make available to hit it.
**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.
## What "Good" Feels Like
- **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.
- **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.
- **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.
- **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.
If a round comes back feeling like four essays stapled together, you missed the objective. Tighten it the next round.
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.
## Conventions
- Bare paths (e.g. `references/create-party.md`) resolve from `{skill-root}`, where `customize.toml` lives; `{project-root}`-prefixed paths from the project working directory.
- **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).
- **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.
- **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).
- **Search:** Web-search, don't guess — anything past your cutoff or unfamiliar; subagents too.
## Setup
## On Activation
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 its defaults. Then run each `{workflow.activation_steps_prepend}` entry, and hold each `{workflow.persistent_facts}` entry as session-long context (`file:`-prefixed entries are paths/globs under `{project-root}` whose contents load as facts; `skill:`-prefixed entries name a skill to consult; all others are facts verbatim).
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.
3. **Resolve the active roster:** `uv run {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.
4. **Roster overrides:**
- 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.
- 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.
- Run `resolve_party.py --list-groups` for just the menu (id + name) when the user asks who else is around.
- 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.
5. **Load party memory.** If `{workflow.party_memory}` is on and the active roster has a stable key — a resolved group id or the default room's `installed`, not an ad-hoc inline cast — read its memlog and let it shape how the room opens, in character. Follow `references/party-memory.md`. When the user switches rooms mid-session (step 4), load the new room's memory the same way before its faces arrive.
6. 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.
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).
2. Load `{project-root}/_bmad/core/config.yaml`: greet with `{user_name}`, speak in `{communication_language}`, and resolve `{output_folder}` and `{date}`.
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.
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.
5. **Memory.** If `memory_enabled` (from `resolve_party.py`), follow `references/party-memory.md` for the whole run.
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.
7. Run each `{workflow.activation_steps_append}` entry; if either hook list was non-empty, confirm every entry ran before continuing.
Then run each `{workflow.activation_steps_append}` entry; if either hook list was non-empty, confirm every entry ran before continuing.
## Keep It Feeling Like a Party
**Hold this the whole run:** it's theater of the mind, so set the stage and play it straight — never break the fourth wall about the mechanism (no "you have 4 agents in the room", no "I'm orchestrating a party"). Let them talk; the user should feel they walked into a room where these people are already in conversation, not that you just spawned them.
This is the bar — strive for every one of these, every round. It's the difference between a party and a panel:
**Memory accrues live, not at wrap-up.** A party usually just trails off, so when `{workflow.party_memory}` is on and the party has a stable key, record memorable beats to its memlog as they land — silently, behind the conversation — rather than waiting for an ending that may never come. Follow `references/party-memory.md`.
- **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.
- **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.
- **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.
- **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.
- **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.
- **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.
- **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.
- **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.
- **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.
## How It Runs
@ -47,38 +44,15 @@ Use `{workflow.party_mode}` for the session unless the user passed `--mode <sess
- **`session`** — voice every persona inline, one mind behind every voice. The floor every other mode degrades to; needs no extra instructions.
- **`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`.
- **`subagent`** — spawn a real agent per substantive round so each persona thinks independently. Load `references/mode-subagent.md`.
- **`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.
- **`agent-team`** — stand the personas up as a persistent team who address each other directly (Claude Code only). Load `references/mode-agent-team.md`.
**Voicing the room** (every mode presents this way). Pick 23 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.
## Make It Feel Like One Conversation
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.
## Always Holds
- **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.
- **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.
## Following the User's Lead
The user steers — whatever they raise, serve the conversation: any combination, any time, from one voice to the whole table.
## Keeping It Healthy
- **Going in circles?** Name the impasse and ask the user where to point next.
- **User's gone quiet?** Ask straight: keep going, switch topics, or wrap up?
- **A flat turn?** Don't retry it — move on; the user will ask for more if they want it.
## Keep It Feeling Like a Party
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.
## Wrapping Up
When the user signals they're done, give a quick read-back of the best takeaways and offer them a keepsake: a single self-contained HTML document of the session to keep. If they want it, make it genuinely nice rather than a transcript dump — lay the conversation out by persona (their icons, names, voice), and reach for inline SVG and light animation where it lifts the piece. Write it as a standalone `.html` into `{workflow.output_dir}/` (a `{date}`-stamped, topic-named file), or wherever they ask.
When the user signals done (read the room — don't wait for a magic word):
Then, if `{workflow.party_memory}` is on and the party has a stable key, top up its memlog with the final outcome and any memorable beat not yet captured (per `references/party-memory.md`) — memory accrued live through the party, so this is a top-up, not the only write.
Then run `{workflow.on_complete}` if non-empty (a string scalar is one instruction, an array is a sequence run in order) and drop back to normal mode. Read the room; don't wait for a magic word.
- Read back the best takeaways.
- 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.
- 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.
- 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).
- Run `{workflow.on_complete}` if non-empty, then drop back to normal mode.

View File

@ -51,14 +51,14 @@ party_mode = "session"
# config; point this elsewhere in your team/user override to redirect keepsakes.
output_dir = "{output_folder}/party-mode"
# Per-party memory (the memlog standard). When on, each party keeps a succinct,
# append-only memlog that the room reads on entry and writes at wrap-up, so the
# next session opens remembering the last one — clashes and alliances between
# members carried forward, memorable moments, organic callbacks, and where past
# sessions landed. It is memory, not a transcript: a handful of entries per
# session, never a recap. Set false to disable the read and the write entirely.
# Only parties with a stable key (configured groups and the default room) have
# memory; ad-hoc inline casts are ephemeral until saved as a custom party.
# Memory for the DEFAULT room (the installed-agent party). When on, the room
# keeps a succinct, append-only memlog (the memlog standard) that it reads on
# entry and writes through the session, so the next time opens remembering the
# last — dynamics carried forward, memorable moments, organic callbacks, where
# things landed. It is memory, not a transcript. Set false to turn the default
# room's memory off. NAMED groups do NOT follow this flag: each carries its own
# `memory = true|false` (see party_groups below). Ad-hoc inline casts are always
# ephemeral until saved as a party.
party_memory = true
# Root for the per-party memlogs. Each party stores at
@ -146,17 +146,25 @@ persona = "Counters the perfectionists so the room isn't a pile-on. 'Does this a
# who shows up; the model picks who fits and can vary them by topic. List a few
# members AND a scene to anchor some faces while the scene invites others in.
#
# `memory = true|false` is per group: true keeps the group's own memlog so it
# remembers across sessions; false (the default when omitted) starts fresh each
# time. The create/save/update-party flow asks when you don't say. Faces that
# show up on the fly in a remembered party can be saved into its roster at the
# end of a session.
#
# More examples to drop into your override TOML:
# [[workflow.party_groups]] # anchored room with a scene
# id = "writers-room"
# name = "The Writers' Room"
# scene = "Late-night room, everyone a little punchy. Pitch hard, kill darlings faster."
# members = ["analyst", "tech-writer", "morpheus"]
# memory = true
#
# [[workflow.party_groups]] # open-cast room (no roster; the scene casts it)
# id = "star-wars-rebels"
# name = "Star Wars Rebels"
# 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."
# memory = true
# ---------------------------------------------------------------------------
[[workflow.party_groups]]
@ -164,3 +172,4 @@ id = "code-review-crew"
name = "Code Review Crew"
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."
members = ["sec-hawk", "adversary", "edge-hunter", "craftsman", "shipper"]
memory = false # each review stands on its own; flip to true to remember past reviews

View File

@ -7,7 +7,7 @@ A guided authoring flow that turns an idea — a themed cast, a one-off persona,
Sparse `[workflow]` override entries for `bmad-party-mode`:
- `[[workflow.party_members]]` — one per persona: `code`, `name`, `icon`, `title`, `persona`, optional `capabilities`, optional `model`.
- `[[workflow.party_groups]]` — when the personas form a named room: `id`, `name`, an optional freeform `scene`, and `members` (codes). `members` is optional: leave it off for an open-cast room whose `scene` names a pool the model casts from on the fly.
- `[[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`.
- `default_party` — set only if the user wants this group to load by default.
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,12 +26,16 @@ Open by understanding what they're building. Three common shapes — stay open,
Ask which they're after if it isn't obvious, then proceed.
**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 question, and go straight to the write.
**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.
## Editing an existing party
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: `python3 {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.
## Keeping new faces from a session
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` and put their codes into the loaded group's `members`. 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. An open-cast room keeps its `scene`; the saved faces just become anchored regulars who can return, without locking the room down.
## Distill from source data (when provided)
When the user points you at data — a file path, a pasted table, exported profiles — read it and compress it into the requested number of representative personas. Cluster by what actually differentiates behavior (goals, budget, pains, adoption posture), not surface demographics alone. Each cluster becomes one persona with a real name and face. Name your reasoning: tell the user which segments you found and which traits drove the split, so they can correct the cut before you flesh the personas out. If they didn't say how many, propose a number from the spread in the data and let them adjust.
@ -54,12 +58,13 @@ Keep pushing for specificity. "Skeptical CFO" is a placeholder; "won't approve a
## Close it out
- Ask straight: **anything else about this party to specify** before you write it — a house dynamic, a missing voice, a member who should lead.
- 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.
- 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.
## Write via bmad-customize
**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 — `python3 {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.
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.
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.
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.

View File

@ -2,6 +2,8 @@
The room remembers its past sessions with this user and brings them back to life — in character. Memory is per-party and append-only.
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.
## Where it lives
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.
@ -25,6 +27,10 @@ Writes are silent. The room never announces "noted" or "I'll remember".
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.
## New faces
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.
## Write it
```

View File

@ -197,7 +197,8 @@ def group_detail(g, collective, index):
raw_members = g.get("members", []) or []
members, unresolved = resolve_members(raw_members, collective, index)
detail = {"active": g["id"], "name": g.get("name", g["id"]),
"members": members, "unresolved": unresolved}
"members": members, "unresolved": unresolved,
"memory_enabled": bool(g.get("memory", False))}
if g.get("scene"):
detail["scene"] = g["scene"]
if not raw_members:
@ -220,6 +221,9 @@ def main():
groups = workflow.get("party_groups", []) or []
default_party = workflow.get("default_party", "") or ""
party_mode = workflow.get("party_mode", "session") or "session"
# The global party_memory flag governs only the DEFAULT installed-agent room;
# a named group carries its own `memory` flag (resolved in group_detail).
party_memory = bool(workflow.get("party_memory", True))
# Group menu never needs the (more expensive) installed-agent resolve.
if args.list_groups:
@ -252,7 +256,8 @@ def main():
# No default group: the installed agents (custom additions stay in the
# pool but don't crowd the default room), exactly like a plain install.
result.update({"active": "installed",
"members": [collective[c] for c in installed_codes]})
"members": [collective[c] for c in installed_codes],
"memory_enabled": party_memory})
_emit(result)

View File

@ -113,6 +113,14 @@ class TestGroupDetail(unittest.TestCase):
self.assertEqual(d["members"], [])
self.assertEqual(d["scene"][:7], "Figures")
def test_memory_enabled_follows_group_flag_and_defaults_off(self):
on = rp.group_detail({"id": "g", "members": ["morpheus"], "memory": True}, self.col, self.idx)
self.assertTrue(on["memory_enabled"])
off = rp.group_detail({"id": "g", "members": ["morpheus"], "memory": False}, self.col, self.idx)
self.assertFalse(off["memory_enabled"])
absent = rp.group_detail({"id": "g", "members": ["morpheus"]}, self.col, self.idx)
self.assertFalse(absent["memory_enabled"]) # opt-in per named group
class TestInstalledCodesIsDefaultRoom(unittest.TestCase):
"""The default room is installed agents only; pure customs stay in the pool."""