diff --git a/docs/guide_mma.md b/docs/guide_mma.md index ee478846..8c2ab92b 100644 --- a/docs/guide_mma.md +++ b/docs/guide_mma.md @@ -35,6 +35,9 @@ class Ticket: depends_on: List[str] = field() # Ticket IDs that must complete first blocked_reason: Optional[str] = None # Why this ticket is blocked step_mode: bool = False # If True, requires manual approval before execution + persona_id: Optional[str] = None # Per-ticket persona override; see Persona Application + retry_count: int = 0 # Increments on failure; drives model escalation + model_override: Optional[str] = None # If set, bypasses persona/model_list selection def mark_blocked(self, reason: str) -> None # Sets status="blocked", stores reason def mark_complete(self) -> None # Sets status="completed" @@ -75,6 +78,8 @@ class WorkerContext: ticket_id: str # Which ticket this worker is processing model_name: str # LLM model to use (e.g., "gemini-2.5-flash-lite") messages: List[dict] # Conversation history for this worker + persona_id: Optional[str] = None # Per-worker persona (set in run_worker_lifecycle) + tool_preset: Optional[str] = None # Fallback tool preset if persona has none ``` --- @@ -173,10 +178,10 @@ class ConductorEngine: self.track = track self.event_queue = event_queue self.tier_usage = { - "Tier 1": {"input": 0, "output": 0, "model": "gemini-3.1-pro-preview"}, - "Tier 2": {"input": 0, "output": 0, "model": "gemini-3-flash-preview"}, - "Tier 3": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite"}, - "Tier 4": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite"}, + "Tier 1": {"input": 0, "output": 0, "model": "gemini-3.1-pro-preview", "tool_preset": None, "persona": None}, + "Tier 2": {"input": 0, "output": 0, "model": "gemini-3-flash-preview", "tool_preset": None, "persona": None}, + "Tier 3": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite", "tool_preset": None, "persona": None}, + "Tier 4": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite", "tool_preset": None, "persona": None}, } self.dag = TrackDAG(self.track.tickets) self.engine = ExecutionEngine(self.dag, auto_queue=auto_queue) @@ -185,6 +190,16 @@ class ConductorEngine: self._pause_event: threading.Event = threading.Event() ``` +**Per-tier `tier_usage` schema** (each tier entry): + +| Key | Type | Purpose | +|---|---|---| +| `input` | `int` | Cumulative input tokens for this tier | +| `output` | `int` | Cumulative output tokens for this tier | +| `model` | `str` | Default model name (overridable per ticket via `model_override` or persona) | +| `tool_preset` | `Optional[str]` | Active tool preset name (set via `set_tool_preset` or persona) | +| `persona` | `Optional[str]` | Active persona name (set when a ticket's persona is applied) | + ### State Broadcast (`_push_state`) On every state change, the engine pushes the full orchestration state to the GUI via `AsyncEventQueue`: @@ -333,6 +348,48 @@ The `confirm_spawn` function uses the `dialog_container` pattern: 4. When the GUI fills in the dialog, call `.wait()` to get the result. 5. Returns `(approved, modified_prompt, modified_context)`. +### Persona Application + +When a ticket has `persona_id` set (or a tier-level persona is active), `run_worker_lifecycle` resolves the persona from `PersonaManager` and applies it before the AI call: + +```python +# Apply Persona if specified +persona = None +if context.persona_id: + pm = PersonaManager(...) + personas = pm.load_all() + if context.persona_id in personas: + persona = personas[context.persona_id] + if persona.system_prompt: + ai_client.set_custom_system_prompt(persona.system_prompt) + if persona.bias_profile: + ai_client.set_bias_profile(persona.bias_profile) + if persona.preferred_models: + preferred_models = persona.preferred_models + if persona.tool_preset: + persona_tool_preset = persona.tool_preset + +# Apply tool preset: use persona's tool_preset if available, otherwise fall back to context.tool_preset +effective_tool_preset = persona_tool_preset or context.tool_preset +``` + +A single persona may override: +- **`system_prompt`** — replaces the default system prompt for the worker +- **`bias_profile`** — influences tool selection via semantic nudging +- **`preferred_models`** — list used for model escalation (replaces the default `models_list`) +- **`tool_preset`** — applied via `set_tool_preset()`; takes precedence over the ticket's `context.tool_preset` +- **`aggregation_strategy`** — sets the file aggregation strategy (`auto`/`full`/`summarize`/`skeleton`) for the worker's context + +**Resolution order at model selection time** (in `run_worker_lifecycle`): + +1. `ticket.model_override` (if set) — used unconditionally +2. `persona.preferred_models` (if persona applied) — first item is the initial model +3. `ticket.retry_count`-indexed entry in the resolved `models_list` — escalates on retries + +If the persona fails to load (file not found, parse error), the worker logs a warning and falls back to the default model list. The persona is **not** a hard failure point. + +See [guide_personas.md](guide_personas.md) (placeholder; written in Task 10) for the full persona schema, scope inheritance rules, and editor modal. + --- ## Tier 4: QA Error Analysis @@ -373,8 +430,9 @@ Each tier operates within its own token budget: - **Tier 3 workers** use lightweight models (default: `gemini-2.5-flash-lite`) and receive only the files listed in `context_requirements`. - **Context Amnesia** ensures no accumulated history bleeds between tickets. -- **Tier 2** tracks cumulative `tier_usage` per tier: `{"input": N, "output": N}` for token cost monitoring. +- **Tier 2** tracks cumulative `tier_usage` per tier: `{"input": N, "output": N, "model": ..., "tool_preset": ..., "persona": ...}` for token cost monitoring and persona attribution. - **First file vs subsequent files**: The first `context_requirements` file gets a curated view (preserving hot paths); subsequent files get only skeletons. +- **RAG augmentation is caller-injected**: The ConductorEngine does not own a RAG engine. The caller (typically `AppController` for the main discussion, or the GUI's RAG panel for project-wide queries) is responsible for instantiating an `RAGEngine` and passing it through to `ai_client.send(rag_engine=...)` for each worker call. See [guide_architecture.md](guide_architecture.md#rag-integration) for the dispatch flow. --- @@ -468,3 +526,39 @@ conductor/tracks// 1. `state.toml` (structured `TrackState`) — counts tasks with `status == "completed"`. 2. `metadata.json` (legacy) — gets id/title/status only. 3. `plan.md` (regex) — counts `- [x]` vs `- [ ]` checkboxes for progress. + +--- + +## Beads Integration (Roadmap) + +[Beads](https://github.com/steveyegge/beads) is a Dolt-backed issue tracking system. The `src/beads_client.py` module provides a Python client for `bd` CLI calls (`bd_create`, `bd_list`, `bd_ready`, `bd_update`). The client is functional but not yet integrated into the `ConductorEngine` execution loop. + +**Current state (as of 2026-06-02):** +- `BeadsClient` is instantiable; it detects whether a project's `.beads/` directory exists and falls back to no-op if not. +- Tools `bd_create`, `bd_list`, `bd_ready`, `bd_update` are exposed via the MCP bridge (see [guide_tools.md](guide_tools.md)). +- The ConductorEngine still writes track state to `conductor/tracks//` (markdown-based), not to a Beads repo. +- A project's TOML may specify a conductor directory override (`[conductor].dir`) but does not yet support a Beads repository path. + +**Planned integration:** +- The ConductorEngine's `parse_json_tickets` would optionally forward ingested tickets to `BeadsClient.bd_create` when Beads mode is active. +- `save_track_state` would write to `.beads/` instead of `conductor/tracks//state.toml` when Beads is active. +- The Visual DAG would query `bd_list` for real-time ticket status instead of the in-memory `TrackDAG`. + +See [guide_beads.md](guide_beads.md) (placeholder; written in Task 10) for the full Beads client API and the toolset exposed to agents. + +--- + +## Workspace Profile Auto-Switching (Roadmap) + +The `WorkspaceManager` (`src/workspace_manager.py`) supports binding workspace profiles to MMA tier context. Currently, profiles can be saved and loaded manually; the auto-switch hook is implemented but not yet wired into `ConductorEngine`. + +**Current state:** +- `WorkspaceProfile` (named docking + window state) can be saved/loaded via the GUI. +- Scope inheritance (Global vs Project) is supported. +- A `bind_to_context(context_id: str, profile_name: str)` method exists on `WorkspaceManager`. + +**Planned integration:** +- On Tier transition (`tier1 → tier2 → tier3`), `ConductorEngine` would call `WorkspaceManager.bind_to_context("tier3", active_profile)` to reshape the UI for the current cognitive load. +- This is opt-in via `[conductor].auto_switch_profiles = true` in `config.toml`. + +See [guide_workspace_profiles.md](guide_workspace_profiles.md) (placeholder; written in Task 10) for the full profile schema.