docs: scrub gemini_cli references from 12 docs (provider count 8->7)

Cleaned: docs/guide_ai_client.md, docs/guide_architecture.md,
docs/guide_models.md, docs/guide_simulations.md,
docs/guide_context_aggregation.md, docs/guide_tools.md, docs/Readme.md,
conductor/tech-stack.md, conductor/product.md,
conductor/product-guidelines.md, conductor/workflow.md,
conductor/code_styleguides/error_handling.md.

Provider list citations updated to 7 (gemini, anthropic, deepseek,
minimax, qwen, grok, llama). guide_meta_boundary.md intentionally
retained (its gemini_cli references are the meta-tooling
GEMINI_CLI_HOOK_CONTEXT env var, NOT the provider; per spec GAP-A12).
This commit is contained in:
ed
2026-07-05 20:16:53 -04:00
parent be93c262e0
commit bd1d966c12
12 changed files with 1242 additions and 1242 deletions
+179 -179
View File
@@ -167,7 +167,7 @@ Need to represent "missing or failed"?
| +-- Use raise (only for programmer errors like KeyError on a known dict) | +-- Use raise (only for programmer errors like KeyError on a known dict)
| |
+-- Does the SDK raise an exception you can't avoid? +-- Does the SDK raise an exception you can't avoid?
+-- Catch at the boundary; convert to ErrorInfo inside a Result +-- Catch at the boundary; convert to ErrorInfo inside a Result
``` ```
--- ---
@@ -177,17 +177,17 @@ Need to represent "missing or failed"?
**DON'T do these things:** **DON'T do these things:**
1. **DON'T** use `Optional[X]` for "this might fail at runtime". Use 1. **DON'T** use `Optional[X]` for "this might fail at runtime". Use
`Result[X]` instead. `Result[X]` instead.
2. **DON'T** use `None` as a sentinel for "no result". Use a nil-sentinel 2. **DON'T** use `None` as a sentinel for "no result". Use a nil-sentinel
dataclass. dataclass.
3. **DON'T** raise a custom exception class for runtime failures. Catch SDK 3. **DON'T** raise a custom exception class for runtime failures. Catch SDK
exceptions and return `ErrorInfo`. exceptions and return `ErrorInfo`.
4. **DON'T** use `Union[T, E]` (sum type). Use a struct with parallel fields 4. **DON'T** use `Union[T, E]` (sum type). Use a struct with parallel fields
(AND over OR). (AND over OR).
5. **DON'T** have `if x is None: handle; else: use_x` patterns in production 5. **DON'T** have `if x is None: handle; else: use_x` patterns in production
code. The nil-sentinel makes them unnecessary. code. The nil-sentinel makes them unnecessary.
6. **DON'T** catch `except Exception` and silently swallow. Convert to 6. **DON'T** catch `except Exception` and silently swallow. Convert to
`ErrorInfo` and return in the `Result`. `ErrorInfo` and return in the `Result`.
--- ---
@@ -196,16 +196,16 @@ Need to represent "missing or failed"?
The 3 refactored subsystems demonstrate each pattern in context: The 3 refactored subsystems demonstrate each pattern in context:
- **`src/mcp_client.py:205-294`** — `read_file`, `list_directory`, - **`src/mcp_client.py:205-294`** — `read_file`, `list_directory`,
`search_files` return `Result[str]`; `(p, err)` tuples become `search_files` return `Result[str]`; `(p, err)` tuples become
`Result[Path]`; the 30+ `assert p is not None` chain (lines 304-794) is `Result[Path]`; the 30+ `assert p is not None` chain (lines 304-794) is
removed. removed.
- **`src/ai_client.py`** — `_send_<vendor>_result()` returns `Result[str]` - **`src/ai_client.py`** — `_send_<vendor>_result()` returns `Result[str]`
(8 vendors: gemini, anthropic, deepseek, minimax, gemini_cli, qwen, llama, (8 vendors: gemini, anthropic, deepseek, minimax, qwen, llama,
grok); `send(...) -> Result[str, ErrorInfo]` is the public API. grok); `send(...) -> Result[str, ErrorInfo]` is the public API.
- **`src/rag_engine.py:100-180`** — `_init_vector_store_result`, - **`src/rag_engine.py:100-180`** — `_init_vector_store_result`,
`_validate_collection_dim_result`, `is_empty_result`, `add_documents_result` `_validate_collection_dim_result`, `is_empty_result`, `add_documents_result`
return `Result[None]` or `Result[T]`; broad `except Exception` blocks return `Result[None]` or `Result[T]`; broad `except Exception` blocks
become `ErrorInfo` entries. become `ErrorInfo` entries.
--- ---
@@ -219,23 +219,23 @@ from `_in_3_files.py` per the contradictions report) covers all
remaining work to bring the 14 migration-target files into compliance. remaining work to bring the 14 migration-target files into compliance.
- **`Optional[T]` return types are FORBIDDEN** in all `src/*.py`. Use - **`Optional[T]` return types are FORBIDDEN** in all `src/*.py`. Use
`Result[T]` (with `NIL_T` singleton if needed) instead. Rationale: `Result[T]` (with `NIL_T` singleton if needed) instead. Rationale:
`Optional[T]` is the sum type `Union[T, None]` that Fleury's framework `Optional[T]` is the sum type `Union[T, None]` that Fleury's framework
replaces. Mixing the two patterns reintroduces the bifurcation the replaces. Mixing the two patterns reintroduces the bifurcation the
convention is designed to remove. convention is designed to remove.
- Argument types that may be `None` (e.g., `rag_engine: Optional[Any] = None`) - Argument types that may be `None` (e.g., `rag_engine: Optional[Any] = None`)
remain allowed; they describe a caller choice, not a runtime failure remain allowed; they describe a caller choice, not a runtime failure
of this function. Only `Optional[T]` *return* types are banned. of this function. Only `Optional[T]` *return* types are banned.
- **Function return types must be `Result[T]` for any function that can fail - **Function return types must be `Result[T]` for any function that can fail
at runtime.** A function that can't fail (e.g., `get_name() -> str`) at runtime.** A function that can't fail (e.g., `get_name() -> str`)
doesn't need a `Result`. The classification is "can this return a different doesn't need a `Result`. The classification is "can this return a different
value under different runtime conditions?" If yes, `Result`. If no, plain value under different runtime conditions?" If yes, `Result`. If no, plain
return type. return type.
- **Catch SDK exceptions at the boundary only.** Inside the 3 refactored - **Catch SDK exceptions at the boundary only.** Inside the 3 refactored
files, the only place an exception is caught is at the SDK call site files, the only place an exception is caught is at the SDK call site
(e.g., `_send_<vendor>_result()` wrapping the SDK call). Internal (e.g., `_send_<vendor>_result()` wrapping the SDK call). Internal
`try/except` is reserved for converting `OSError`, `PermissionError`, and `try/except` is reserved for converting `OSError`, `PermissionError`, and
similar I/O exceptions to `ErrorInfo` at the mcp_client tool boundary. similar I/O exceptions to `ErrorInfo` at the mcp_client tool boundary.
The verification script `scripts/audit_optional_returns.py` enforces the The verification script `scripts/audit_optional_returns.py` enforces the
`Optional[X]` rule by failing CI if any new `Optional[X]` return type `Optional[X]` rule by failing CI if any new `Optional[X]` return type
@@ -266,18 +266,18 @@ warnings use `warnings.warn(..., stacklevel=2)` which is thread-safe.
**Use it for:** **Use it for:**
- New public APIs (any function that can fail at runtime and the caller - New public APIs (any function that can fail at runtime and the caller
might care). might care).
- New internal functions where the caller benefits from knowing the failure - New internal functions where the caller benefits from knowing the failure
(vs. just propagating `None`). (vs. just propagating `None`).
**Don't use it for:** **Don't use it for:**
- Constructors (`__init__`) that fail with programmer errors (use `assert` or - Constructors (`__init__`) that fail with programmer errors (use `assert` or
`raise` for these). See "Constructors Can Raise" below for the full rule. `raise` for these). See "Constructors Can Raise" below for the full rule.
- Trivial getters that can't fail (`get_name() -> str` doesn't need a - Trivial getters that can't fail (`get_name() -> str` doesn't need a
`Result`). `Result`).
- Performance-critical hot paths where the overhead of the dataclass - Performance-critical hot paths where the overhead of the dataclass
allocation is measurable (rare; benchmark first). allocation is measurable (rare; benchmark first).
--- ---
@@ -333,16 +333,16 @@ into internal code; it's the framework contract.
# Compliant: FastAPI boundary in _api_* handler # Compliant: FastAPI boundary in _api_* handler
async def _api_get_key(controller, header_key: str) -> str: async def _api_get_key(controller, header_key: str) -> str:
if not _is_valid_key(header_key): if not _is_valid_key(header_key):
raise HTTPException(status_code=403, detail="Could not validate API Key") raise HTTPException(status_code=403, detail="Could not validate API Key")
return header_key return header_key
# Compliant: broad catch + HTTPException at the FastAPI boundary # Compliant: broad catch + HTTPException at the FastAPI boundary
async def _api_generate(controller, payload): async def _api_generate(controller, payload):
try: try:
result = ai_client.send(...) result = ai_client.send(...)
return result.data return result.data
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"AI call failed: {e}") raise HTTPException(status_code=500, detail=f"AI call failed: {e}")
``` ```
The catch-all `except Exception` is acceptable here **because the The catch-all `except Exception` is acceptable here **because the
@@ -353,13 +353,13 @@ HTTP status code is the framework contract.
### What is NOT a boundary ### What is NOT a boundary
- Internal business logic: `try/except` around a `for` loop in a - Internal business logic: `try/except` around a `for` loop in a
controller method is internal, not boundary. controller method is internal, not boundary.
- Cross-method calls within `src/`: calling a method in - Cross-method calls within `src/`: calling a method in
`app_controller.py` from a method in `app_controller.py` is internal, `app_controller.py` from a method in `app_controller.py` is internal,
not boundary. not boundary.
- stdlib I/O that the user controls directly: opening a file the user - stdlib I/O that the user controls directly: opening a file the user
passed via `--config` is internal; converting the failure should be passed via `--config` is internal; converting the failure should be
Result-based, not exception-based. Result-based, not exception-based.
--- ---
@@ -391,10 +391,10 @@ where the caller of the drain point does NOT need to receive a
```python ```python
# COMPLIANT: drain point. The HTTP status code IS the error response. # COMPLIANT: drain point. The HTTP status code IS the error response.
async def _api_get_track(controller, track_id: str) -> dict: async def _api_get_track(controller, track_id: str) -> dict:
result = controller.get_track_result(track_id) result = controller.get_track_result(track_id)
if not result.ok: if not result.ok:
raise HTTPException(status_code=404, detail=result.errors[0].ui_message()) raise HTTPException(status_code=404, detail=result.errors[0].ui_message())
return {"track": result.data} return {"track": result.data}
``` ```
The caller (the HTTP client) receives an HTTP 4xx/5xx response. The The caller (the HTTP client) receives an HTTP 4xx/5xx response. The
@@ -407,10 +407,10 @@ the error.
```python ```python
# COMPLIANT: drain point. The user sees the error in the modal. # COMPLIANT: drain point. The user sees the error in the modal.
def _show_track_load_failure(controller, track_id: str) -> None: def _show_track_load_failure(controller, track_id: str) -> None:
result = controller.get_track_result(track_id) result = controller.get_track_result(track_id)
if not result.ok: if not result.ok:
imgui.open_popup("Track Load Error") imgui.open_popup("Track Load Error")
# popup body reads result.errors[0].ui_message() and displays it # popup body reads result.errors[0].ui_message() and displays it
``` ```
The user sees the error. The caller (`_show_track_load_failure`) The user sees the error. The caller (`_show_track_load_failure`)
@@ -421,10 +421,10 @@ returns `None` — it is the end of the propagation chain.
```python ```python
# COMPLIANT: drain point. The app shuts down intentionally. # COMPLIANT: drain point. The app shuts down intentionally.
def _shutdown_on_critical_failure(controller) -> None: def _shutdown_on_critical_failure(controller) -> None:
result = controller._init_session_db_result() result = controller._init_session_db_result()
if not result.ok: if not result.ok:
sys.stderr.write(f"FATAL: {result.errors[0].ui_message()}\n") sys.stderr.write(f"FATAL: {result.errors[0].ui_message()}\n")
sys.exit(1) sys.exit(1)
``` ```
The error is propagated to the OS via `sys.exit(1)`. The drain point The error is propagated to the OS via `sys.exit(1)`. The drain point
@@ -435,12 +435,12 @@ is the process termination itself.
```python ```python
# COMPLIANT: drain point. The error is sent to monitoring. # COMPLIANT: drain point. The error is sent to monitoring.
def _report_failure_to_telemetry(controller, op_name: str, result: Result[T]) -> None: def _report_failure_to_telemetry(controller, op_name: str, result: Result[T]) -> None:
if not result.ok: if not result.ok:
telemetry.emit_error( telemetry.emit_error(
operation=op_name, operation=op_name,
kind=result.errors[0].kind.value, kind=result.errors[0].kind.value,
message=result.errors[0].message, message=result.errors[0].message,
) )
``` ```
The error reaches the telemetry system. The caller of the drain point The error reaches the telemetry system. The caller of the drain point
@@ -452,12 +452,12 @@ receives `None`.
# COMPLIANT: drain point. The retry is bounded and the final failure # COMPLIANT: drain point. The retry is bounded and the final failure
# is reported back to the user (which is itself a drain point). # is reported back to the user (which is itself a drain point).
def _load_track_with_retry(controller, track_id: str) -> Track | None: def _load_track_with_retry(controller, track_id: str) -> Track | None:
for attempt in range(MAX_RETRIES): for attempt in range(MAX_RETRIES):
result = controller.get_track_result(track_id) result = controller.get_track_result(track_id)
if result.ok: if result.ok:
return result.data return result.data
time.sleep(BACKOFF_SECONDS * (attempt + 1)) time.sleep(BACKOFF_SECONDS * (attempt + 1))
return None # Caller will display "failed after N attempts" return None # Caller will display "failed after N attempts"
``` ```
The retry loop is a drain point: the function returns `Track | None` The retry loop is a drain point: the function returns `Track | None`
@@ -471,15 +471,15 @@ The following are **NOT** drain points. They are silent-fallback
violations that lose data: violations that lose data:
- **`sys.stderr.write(...)` alone** (without visible user feedback or - **`sys.stderr.write(...)` alone** (without visible user feedback or
app-level decision): the data is lost; the user sees nothing. app-level decision): the data is lost; the user sees nothing.
Logging is NOT a drain. Logging is NOT a drain.
- **`logging.error(...)` / `logger.exception(...)` alone**: same as - **`logging.error(...)` / `logger.exception(...)` alone**: same as
above. The log is recorded, but the error is invisible to the user. above. The log is recorded, but the error is invisible to the user.
- **`return default_value`** after a `try/except`: the original error - **`return default_value`** after a `try/except`: the original error
context is lost; the caller cannot distinguish success from failure. context is lost; the caller cannot distinguish success from failure.
- **`pass`**: silent. The data is lost. - **`pass`**: silent. The data is lost.
- **`traceback.print_exc(...)` alone**: similar to logging — visible in - **`traceback.print_exc(...)` alone**: similar to logging — visible in
the console but invisible to the user. the console but invisible to the user.
**The key distinction:** a drain point **terminates the propagation** **The key distinction:** a drain point **terminates the propagation**
with a visible, intentional action. A log call or silent fallback with a visible, intentional action. A log call or silent fallback
@@ -490,13 +490,13 @@ with a visible, intentional action. A log call or silent fallback
The two concepts are complementary: The two concepts are complementary:
- **Boundary types** (Section: "Boundary Types") describe WHERE - **Boundary types** (Section: "Boundary Types") describe WHERE
exceptions originate or are converted (third-party SDK calls, stdlib exceptions originate or are converted (third-party SDK calls, stdlib
I/O, FastAPI handlers). The catch site at a boundary converts the I/O, FastAPI handlers). The catch site at a boundary converts the
exception to `ErrorInfo` and returns it in `Result`. exception to `ErrorInfo` and returns it in `Result`.
- **Drain points** describe WHERE the `Result[T]` propagation - **Drain points** describe WHERE the `Result[T]` propagation
terminates (HTTP error response, GUI display, app termination, terminates (HTTP error response, GUI display, app termination,
telemetry, bounded retry). The function at a drain point returns telemetry, bounded retry). The function at a drain point returns
`None` or raises into a framework; it does NOT return `Result[T]`. `None` or raises into a framework; it does NOT return `Result[T]`.
A function can be BOTH a boundary AND a drain point. The A function can be BOTH a boundary AND a drain point. The
`_api_*` FastAPI handler is a boundary (catches SDK exceptions) and a `_api_*` FastAPI handler is a boundary (catches SDK exceptions) and a
@@ -510,12 +510,12 @@ Heuristic D that recognizes drain-point patterns as `INTERNAL_COMPLIANT`.
The patterns are: The patterns are:
1. `except (SomeError): self.send_response(status); ...` (HTTP 1. `except (SomeError): self.send_response(status); ...` (HTTP
response in a `BaseHTTPRequestHandler` subclass) response in a `BaseHTTPRequestHandler` subclass)
2. `except (SomeError): imgui.open_popup(...)` (GUI error display) 2. `except (SomeError): imgui.open_popup(...)` (GUI error display)
3. `except (SomeError): sys.exit(...)` (intentional termination) 3. `except (SomeError): sys.exit(...)` (intentional termination)
4. `except (SomeError): telemetry.emit_*(...)` (telemetry) 4. `except (SomeError): telemetry.emit_*(...)` (telemetry)
5. `except (SomeError): for attempt in range(N): ...; return None` 5. `except (SomeError): for attempt in range(N): ...; return None`
(bounded retry; followed by `return None` or similar end-of-propagation) (bounded retry; followed by `return None` or similar end-of-propagation)
A site matching any of these is classified `INTERNAL_COMPLIANT`, with a A site matching any of these is classified `INTERNAL_COMPLIANT`, with a
note that the pattern is a drain point. note that the pattern is a drain point.
@@ -554,18 +554,18 @@ calls):
```python ```python
def _validate_collection_dim_result(self) -> Result[None]: def _validate_collection_dim_result(self) -> Result[None]:
if self.collection is None or self.collection == "mock": if self.collection is None or self.collection == "mock":
return Result(data=None) return Result(data=None)
try: try:
res = self.collection.get(limit=1, include=["embeddings"]) res = self.collection.get(limit=1, include=["embeddings"])
# ... validation logic ... # ... validation logic ...
return Result(data=None) return Result(data=None)
except Exception as e: except Exception as e:
return Result(data=None, errors=[ return Result(data=None, errors=[
ErrorInfo(kind=ErrorKind.INTERNAL, ErrorInfo(kind=ErrorKind.INTERNAL,
message=f"Failed to validate collection dim: {e}", message=f"Failed to validate collection dim: {e}",
source="rag._validate_collection_dim", source="rag._validate_collection_dim",
original=e) original=e)
]) ])
``` ```
This `except Exception` is **compliant** because the catch + ErrorInfo This `except Exception` is **compliant** because the catch + ErrorInfo
@@ -602,11 +602,11 @@ elaborates.
```python ```python
class MyClass: class MyClass:
def __init__(self, config: Config): def __init__(self, config: Config):
if config is None: if config is None:
raise ValueError("MyClass requires a non-None Config") raise ValueError("MyClass requires a non-None Config")
if not config.api_key: if not config.api_key:
raise ValueError("MyClass requires a non-empty api_key") raise ValueError("MyClass requires a non-empty api_key")
self._config = config self._config = config
``` ```
**Compliant assert (for impossible states):** **Compliant assert (for impossible states):**
@@ -674,7 +674,7 @@ try:
do_something(resource) do_something(resource)
finally: finally:
release(resource) # `finally` is cleaner; `except+raise` is for when release(resource) # `finally` is cleaner; `except+raise` is for when
# you also need to log or convert # you also need to log or convert
``` ```
Use `try/finally` for the pure cleanup case (no logging/conversion). Use `try/finally` for the pure cleanup case (no logging/conversion).
@@ -759,15 +759,15 @@ script's output):
Files scanned: 65 Files scanned: 65
Files with findings: 42 Files with findings: 42
Total sites: 348 Total sites: 348
Compliant sites: 80 Compliant sites: 80
Suspicious sites: 25 Suspicious sites: 25
Violation sites: 211 Violation sites: 211
Unclear (review): 32 Unclear (review): 32
--- Baseline (refactored files: mcp_client, ai_client, rag_engine) --- --- Baseline (refactored files: mcp_client, ai_client, rag_engine) ---
Sites: 112, violations: 77 Sites: 112, violations: 77
--- Migration target (all other src/ files) --- --- Migration target (all other src/ files) ---
Sites: 236, violations: 134 Sites: 236, violations: 134
``` ```
The **baseline** is the 3 fully-refactored files (the convention reference). The **baseline** is the 3 fully-refactored files (the convention reference).
@@ -788,15 +788,15 @@ When converting existing code:
1. Identify the `Optional[X]` return type or the `raise` statement. 1. Identify the `Optional[X]` return type or the `raise` statement.
2. Define a `Result` dataclass (or use the existing one) with `data: X` and 2. Define a `Result` dataclass (or use the existing one) with `data: X` and
`errors: list[ErrorInfo]`. `errors: list[ErrorInfo]`.
3. Replace `None` returns with `Result(data=NIL_X, errors=[...])` or 3. Replace `None` returns with `Result(data=NIL_X, errors=[...])` or
`Result(data=zero_value, errors=[...])`. `Result(data=zero_value, errors=[...])`.
4. Replace `raise X` with 4. Replace `raise X` with
`return Result(data=zero_value, errors=[ErrorInfo(kind=..., message=...)])`. `return Result(data=zero_value, errors=[ErrorInfo(kind=..., message=...)])`.
5. Update the caller to check `result.errors` instead of `is None` / 5. Update the caller to check `result.errors` instead of `is None` /
`try/except`. `try/except`.
6. Add a test that verifies both the success and failure paths return the 6. Add a test that verifies both the success and failure paths return the
right `Result`. right `Result`.
--- ---
@@ -816,13 +816,13 @@ When a function is migrated from `Optional[T]` / `raise` to `Result[T]`:
```python ```python
# BEFORE (the legacy): # BEFORE (the legacy):
def do_thing() -> Optional[str]: def do_thing() -> Optional[str]:
result = do_thing_result() result = do_thing_result()
if not result.ok: return None if not result.ok: return None
return result.data return result.data
# AFTER (the new): # AFTER (the new):
def do_thing_result() -> Result[str]: def do_thing_result() -> Result[str]:
... ...
``` ```
The `do_thing` function must be **deleted**, not kept as a wrapper. Keep only one entry point: `do_thing_result()`. The `do_thing` function must be **deleted**, not kept as a wrapper. Keep only one entry point: `do_thing_result()`.
@@ -832,7 +832,7 @@ The `do_thing` function must be **deleted**, not kept as a wrapper. Keep only on
```python ```python
# After OBLITERATE: only do_thing_result exists # After OBLITERATE: only do_thing_result exists
def do_thing_result() -> Result[str]: def do_thing_result() -> Result[str]:
... ...
``` ```
Callers are rewritten: Callers are rewritten:
@@ -881,18 +881,18 @@ checklist before claiming a task is done.**
**Before writing or modifying ANY `try/except` code, you MUST:** **Before writing or modifying ANY `try/except` code, you MUST:**
1. **READ `conductor/code_styleguides/error_handling.md` end-to-end.** 1. **READ `conductor/code_styleguides/error_handling.md` end-to-end.**
The 7 sections are: (1) The 5 Patterns, (2) Decision Tree, The 7 sections are: (1) The 5 Patterns, (2) Decision Tree,
(3) Anti-Patterns, (4) Hard Rules, (5) Boundary Types, (6) The (3) Anti-Patterns, (4) Hard Rules, (5) Boundary Types, (6) The
Broad-Except Distinction, (7) AI Agent Checklist (this section). Broad-Except Distinction, (7) AI Agent Checklist (this section).
2. **Acknowledge the read in the commit message.** Format: "TIER-2 2. **Acknowledge the read in the commit message.** Format: "TIER-2
READ conductor/code_styleguides/error_handling.md before READ conductor/code_styleguides/error_handling.md before
<phase/task>." <phase/task>."
3. **The styleguide is the source of truth.** Your training data is 3. **The styleguide is the source of truth.** Your training data is
the OPPOSITE of this convention. Idiomatic Python (`try/except` + the OPPOSITE of this convention. Idiomatic Python (`try/except` +
`Optional[T]` + `raise Exception`) is what the convention is `Optional[T]` + `raise Exception`) is what the convention is
designed to REPLACE. designed to REPLACE.
**Why:** the previous round (Phase 10) added 5 LAUNDERING HEURISTICS to **Why:** the previous round (Phase 10) added 5 LAUNDERING HEURISTICS to
the audit script that classified narrowing as compliant, which is the the audit script that classified narrowing as compliant, which is the
@@ -906,75 +906,75 @@ re-introducing laundering heuristics.**
When writing NEW code, you MUST: When writing NEW code, you MUST:
1. **Use `Result[T]` for any function that can fail at runtime.** A 1. **Use `Result[T]` for any function that can fail at runtime.** A
function that returns a different value under different runtime function that returns a different value under different runtime
conditions (success vs. failure) returns `Result[T]`, not conditions (success vs. failure) returns `Result[T]`, not
`Optional[T]`, not `T | None`, not a custom exception class. Use the `Optional[T]`, not `T | None`, not a custom exception class. Use the
`Result` dataclass from `src/result_types.py`; populate `Result` dataclass from `src/result_types.py`; populate
`errors: list[ErrorInfo]` on failure. `errors: list[ErrorInfo]` on failure.
2. **Catch SDK exceptions at the boundary, convert to `ErrorInfo`.** If 2. **Catch SDK exceptions at the boundary, convert to `ErrorInfo`.** If
your code calls `anthropic`, `google.genai`, `openai`, `chromadb`, your code calls `anthropic`, `google.genai`, `openai`, `chromadb`,
`requests`, or any other third-party SDK, the catch site `requests`, or any other third-party SDK, the catch site
converts the exception to `ErrorInfo(kind=..., message=...)` and converts the exception to `ErrorInfo(kind=..., message=...)` and
returns it in `Result.errors`. Do NOT re-raise; do NOT swallow; returns it in `Result.errors`. Do NOT re-raise; do NOT swallow;
do NOT let the exception propagate into internal code. do NOT let the exception propagate into internal code.
3. **Use nil-sentinel dataclasses for "no result".** If a function 3. **Use nil-sentinel dataclasses for "no result".** If a function
would return `None` in idiomatic Python, return a frozen would return `None` in idiomatic Python, return a frozen
`NilPath` / `NilRAGState` / etc. singleton from `NilPath` / `NilRAGState` / etc. singleton from
`src/result_types.py` instead. Callers don't need `if x is None:` `src/result_types.py` instead. Callers don't need `if x is None:`
checks; they can call `x.read_text` and get `""` on the nil path. checks; they can call `x.read_text` and get `""` on the nil path.
4. **Use `try/finally` (no except) for cleanup.** Bare 4. **Use `try/finally` (no except) for cleanup.** Bare
`try: ...; finally: cleanup()` is the canonical `goto defer` `try: ...; finally: cleanup()` is the canonical `goto defer`
pattern. Use it for resource cleanup, lock release, file handle pattern. Use it for resource cleanup, lock release, file handle
close. Do NOT use `try/except` + pass for cleanup; the cleanup close. Do NOT use `try/except` + pass for cleanup; the cleanup
should run whether or not an exception occurred. should run whether or not an exception occurred.
5. **`raise` is reserved for programmer errors.** `assert` for 5. **`raise` is reserved for programmer errors.** `assert` for
"this should never happen" invariants. `raise ValueError`, "this should never happen" invariants. `raise ValueError`,
`raise NotImplementedError`, `raise KeyError` in `__init__` for `raise NotImplementedError`, `raise KeyError` in `__init__` for
"this object needs X." Do NOT use `raise` for runtime failures "this object needs X." Do NOT use `raise` for runtime failures
(the network is down, the file doesn't exist, the API rate-limited); (the network is down, the file doesn't exist, the API rate-limited);
those are `Result` cases. those are `Result` cases.
### The 7 MUST-NOT-DO rules ### The 7 MUST-NOT-DO rules
When writing NEW code, you MUST NOT: When writing NEW code, you MUST NOT:
1. **DO NOT use `Optional[T]` as a return type** (in any file in 1. **DO NOT use `Optional[T]` as a return type** (in any file in
`src/`). Use `Result[T]` instead. CI fails if you add a new `src/`). Use `Result[T]` instead. CI fails if you add a new
`Optional[T]` return type to any `src/*.py` (enforced by `Optional[T]` return type to any `src/*.py` (enforced by
`scripts/audit_optional_in_baseline_files.py --strict`, `scripts/audit_optional_in_baseline_files.py --strict`,
which scans all `src/*.py` as of 2026-06-27). which scans all `src/*.py` as of 2026-06-27).
2. **DO NOT use `Optional[T]` as a return type** (anywhere else in 2. **DO NOT use `Optional[T]` as a return type** (anywhere else in
`src/`). The convention is migrating to `Result[T]`; new code `src/`). The convention is migrating to `Result[T]`; new code
should set the pattern, not perpetuate the old one. Argument should set the pattern, not perpetuate the old one. Argument
types that may be `None` (caller choice) are still OK. types that may be `None` (caller choice) are still OK.
3. **DO NOT use `None` as a sentinel for "no result".** Use a 3. **DO NOT use `None` as a sentinel for "no result".** Use a
nil-sentinel dataclass. The data is zero-initialized; the caller nil-sentinel dataclass. The data is zero-initialized; the caller
doesn't need a None check. doesn't need a None check.
4. **DO NOT raise a custom exception class for runtime failures.** 4. **DO NOT raise a custom exception class for runtime failures.**
SDK exceptions caught and converted to `ErrorInfo` is the only SDK exceptions caught and converted to `ErrorInfo` is the only
legitimate exception path. Internal code uses `Result`. legitimate exception path. Internal code uses `Result`.
5. **DO NOT use `Union[T, E]` (sum type).** Use `Result[T]` with 5. **DO NOT use `Union[T, E]` (sum type).** Use `Result[T]` with
side-channel `errors: list[ErrorInfo]`. The result is the data side-channel `errors: list[ErrorInfo]`. The result is the data
AND the errors, not a tagged sum. AND the errors, not a tagged sum.
6. **DO NOT catch `except Exception` and silently swallow.** Either 6. **DO NOT catch `except Exception` and silently swallow.** Either
narrow the exception type, convert to `ErrorInfo` in a `Result`, narrow the exception type, convert to `ErrorInfo` in a `Result`,
or document the intentional swallow with a comment-free `assert` or document the intentional swallow with a comment-free `assert`
for the precondition. The audit script flags this as for the precondition. The audit script flags this as
`INTERNAL_SILENT_SWALLOW`. `INTERNAL_SILENT_SWALLOW`.
7. **DO NOT catch `except Exception` in non-`*_result` code without 7. **DO NOT catch `except Exception` in non-`*_result` code without
conversion to `ErrorInfo`.** If you must catch, convert: conversion to `ErrorInfo`.** If you must catch, convert:
`except SomeError as e: return Result(data=NIL_T, errors=[ErrorInfo(kind=INTERNAL, message=..., original=e)])`. `except SomeError as e: return Result(data=NIL_T, errors=[ErrorInfo(kind=INTERNAL, message=..., original=e)])`.
The audit script flags this as `INTERNAL_BROAD_CATCH`. The audit script flags this as `INTERNAL_BROAD_CATCH`.
### The 3 boundary patterns (where `try/except` IS the right answer) ### The 3 boundary patterns (where `try/except` IS the right answer)
@@ -982,20 +982,20 @@ These are the 3 categories where `try/except` is legitimate. See the
"Boundary Types" section above for the full discussion. "Boundary Types" section above for the full discussion.
1. **Third-party SDK calls.** Wrapping `anthropic.Anthropic().messages.create(...)` 1. **Third-party SDK calls.** Wrapping `anthropic.Anthropic().messages.create(...)`
in `try/except anthropic.APIError` is the canonical pattern. in `try/except anthropic.APIError` is the canonical pattern.
Convert to `ErrorInfo`; return in `Result`. Convert to `ErrorInfo`; return in `Result`.
2. **Stdlib I/O that can raise.** `open()`, `os.path.*`, 2. **Stdlib I/O that can raise.** `open()`, `os.path.*`,
`json.loads()`, `subprocess.run()`, `socket.*`, `sqlite3.*`, `json.loads()`, `subprocess.run()`, `socket.*`, `sqlite3.*`,
`chromadb.PersistentClient()` can all raise. Catch the specific `chromadb.PersistentClient()` can all raise. Catch the specific
exception (`OSError`, `FileNotFoundError`, `json.JSONDecodeError`, exception (`OSError`, `FileNotFoundError`, `json.JSONDecodeError`,
`subprocess.CalledProcessError`, etc.); convert to `ErrorInfo`. `subprocess.CalledProcessError`, etc.); convert to `ErrorInfo`.
3. **FastAPI `HTTPException` in `_api_*` handlers.** `raise 3. **FastAPI `HTTPException` in `_api_*` handlers.** `raise
HTTPException(status_code=..., detail=...)` in a function named HTTPException(status_code=..., detail=...)` in a function named
`_api_*` is the FastAPI-idiomatic way to signal HTTP errors. `_api_*` is the FastAPI-idiomatic way to signal HTTP errors.
FastAPI converts it to a JSON response at the framework level. FastAPI converts it to a JSON response at the framework level.
This is NOT an exception leak; it's the framework contract. This is NOT an exception leak; it's the framework contract.
### The pre-commit gate ### The pre-commit gate
@@ -1038,14 +1038,14 @@ automated check; the checklist is the manual one.
--- ---
- `conductor/tracks/data_oriented_error_handling_20260606/spec.md` — the spec - `conductor/tracks/data_oriented_error_handling_20260606/spec.md` — the spec
that established this convention. that established this convention.
- `docs/guide_ai_client.md` "Data-Oriented Error Handling (Fleury Pattern)" - `docs/guide_ai_client.md` "Data-Oriented Error Handling (Fleury Pattern)"
— the in-context guide for the provider layer. — the in-context guide for the provider layer.
- `docs/guide_mcp_client.md` "Data-Oriented Error Handling (Fleury Pattern)" - `docs/guide_mcp_client.md` "Data-Oriented Error Handling (Fleury Pattern)"
— the in-context guide for the MCP tool layer. — the in-context guide for the MCP tool layer.
- `conductor/code_styleguides/data_oriented_design.md` (added 2026-06-12) — the canonical Data-Oriented Design (DOD) reference; this track is the canonical application of DOD to error handling ("errors are data, not control flow"). - `conductor/code_styleguides/data_oriented_design.md` (added 2026-06-12) — the canonical Data-Oriented Design (DOD) reference; this track is the canonical application of DOD to error handling ("errors are data, not control flow").
- `conductor/code_styleguides/agent_memory_dimensions.md` (added 2026-06-12) — the 4-dim memory model; the knowledge harvest TDD protocol in `workflow.md` uses this track's `Result` pattern. - `conductor/code_styleguides/agent_memory_dimensions.md` (added 2026-06-12) — the 4-dim memory model; the knowledge harvest TDD protocol in `workflow.md` uses this track's `Result` pattern.
- `docs/guide_rag.md` "Data-Oriented Error Handling (Fleury Pattern)" — the - `docs/guide_rag.md` "Data-Oriented Error Handling (Fleury Pattern)" — the
in-context guide for the RAG engine. in-context guide for the RAG engine.
- Ryan Fleury's [original article](https://www.dgtlgrove.com/p/the-easiest-way-to-handle-errors) - Ryan Fleury's [original article](https://www.dgtlgrove.com/p/the-easiest-way-to-handle-errors)
— the philosophical foundation. — the philosophical foundation.
+19 -19
View File
@@ -28,12 +28,12 @@ The canonical mandate is in `conductor/code_styleguides/data_oriented_design.md`
## Code Standards & Architecture ## Code Standards & Architecture
- **Data-Oriented & Immediate Mode Heuristics:** Align with the architectural values of engineers like Casey Muratori and Mike Acton. - **Data-Oriented & Immediate Mode Heuristics:** Align with the architectural values of engineers like Casey Muratori and Mike Acton.
- **The "Less Python Does, the Better" Rule:** Python should act primarily as a procedural semantic definer (similar to how ImGui defines a UI DAG), delegating heavy lifting to efficient data structures, vectorized operations, or lower-level primitives. - **The "Less Python Does, the Better" Rule:** Python should act primarily as a procedural semantic definer (similar to how ImGui defines a UI DAG), delegating heavy lifting to efficient data structures, vectorized operations, or lower-level primitives.
- Minimize Python JIT overhead by favoring bulk data processing over fine-grained object-oriented manipulation. - Minimize Python JIT overhead by favoring bulk data processing over fine-grained object-oriented manipulation.
- The GUI (`gui_2.py`) must remain a pure visualization of application state. It should not *own* complex business logic or orchestrator hooks (strive to decouple the 'Application' controller from the 'View'). - The GUI (`gui_2.py`) must remain a pure visualization of application state. It should not *own* complex business logic or orchestrator hooks (strive to decouple the 'Application' controller from the 'View').
- Treat the UI as an immediate mode frame-by-frame projection of underlying data structures. - Treat the UI as an immediate mode frame-by-frame projection of underlying data structures.
- Optimize for zero lag and never block the main render loop with heavy Python JIT work. - Optimize for zero lag and never block the main render loop with heavy Python JIT work.
- Utilize proper asynchronous batching and queue-based pipelines for background AI work, ensuring a data-oriented flow rather than tangled object-oriented state graphs. - Utilize proper asynchronous batching and queue-based pipelines for background AI work, ensuring a data-oriented flow rather than tangled object-oriented state graphs.
- **Strict State Management:** There must be a rigorous separation between the Main GUI rendering thread and daemon execution threads. The UI should *never* hang during AI communication or script execution. Use lock-protected queues and events for synchronization. - **Strict State Management:** There must be a rigorous separation between the Main GUI rendering thread and daemon execution threads. The UI should *never* hang during AI communication or script execution. Use lock-protected queues and events for synchronization.
- **Comprehensive Logging:** Aggressively log all actions, API payloads, tool calls, and executed scripts. Maintain timestamped JSON-L and markdown logs to ensure total transparency and debuggability. - **Comprehensive Logging:** Aggressively log all actions, API payloads, tool calls, and executed scripts. Maintain timestamped JSON-L and markdown logs to ensure total transparency and debuggability.
- **Mandatory ImGui Verification:** All changes to the GUI (`gui_2.py`) MUST be verified using the custom AST linter (`scripts/check_imgui_scopes.py`) to ensure all ImGui scopes (begin/end, push/pop) are properly matched. Developers should prioritize the use of `src/imgui_scopes.py` context managers (`imscope`) over manual push/pop calls. - **Mandatory ImGui Verification:** All changes to the GUI (`gui_2.py`) MUST be verified using the custom AST linter (`scripts/check_imgui_scopes.py`) to ensure all ImGui scopes (begin/end, push/pop) are properly matched. Developers should prioritize the use of `src/imgui_scopes.py` context managers (`imscope`) over manual push/pop calls.
@@ -57,8 +57,8 @@ For the **Indentation** and **Newlines** rules (1-space indent, blank-line rules
- **Region Blocks:** Use `#region: Name` and `#endregion: Name` to logically organize massive files that cannot be easily broken apart without increasing context load. - **Region Blocks:** Use `#region: Name` and `#endregion: Name` to logically organize massive files that cannot be easily broken apart without increasing context load.
- **Type Hinting:** Mandatory, strict type hints for all parameters, return types, and global variables to ensure high-signal context for AI agents. - **Type Hinting:** Mandatory, strict type hints for all parameters, return types, and global variables to ensure high-signal context for AI agents.
- **Structural Dependency Mapping (SDM):** All major state variables, methods, and functions MUST include terse dependency tags at the end of their docstrings for AI-assisted impact analysis. - **Structural Dependency Mapping (SDM):** All major state variables, methods, and functions MUST include terse dependency tags at the end of their docstrings for AI-assisted impact analysis.
- **Functions/Methods:** `[C: Caller1, Caller2]` (Primary callers). - **Functions/Methods:** `[C: Caller1, Caller2]` (Primary callers).
- **State Variables:** `[M: File:Line, Method]` (Mutation points) and `[U: File]` (Major use paths). - **State Variables:** `[M: File:Line, Method]` (Mutation points) and `[U: File]` (Major use paths).
## Data-Oriented Error Handling ## Data-Oriented Error Handling
@@ -89,23 +89,23 @@ are trained on idiomatic Python and will revert to it without explicit
guidance. The project enforces the convention through 4 mechanisms: guidance. The project enforces the convention through 4 mechanisms:
1. **`conductor/code_styleguides/error_handling.md`** — the canonical 1. **`conductor/code_styleguides/error_handling.md`** — the canonical
styleguide. Has 5 patterns, 3 boundary types, 1 broad-except styleguide. Has 5 patterns, 3 boundary types, 1 broad-except
distinction rule, 1 constructor-raise rule, 1 re-raise rule, and distinction rule, 1 constructor-raise rule, 1 re-raise rule, and
the audit script reference. Read this before writing any code that the audit script reference. Read this before writing any code that
can fail at runtime. can fail at runtime.
2. **`conductor/code_styleguides/error_handling.md` "AI Agent Checklist"** — 2. **`conductor/code_styleguides/error_handling.md` "AI Agent Checklist"** —
the explicit cheatsheet of 5 MUST-DO rules, 7 MUST-NOT-DO rules, and the explicit cheatsheet of 5 MUST-DO rules, 7 MUST-NOT-DO rules, and
3 boundary patterns. Run this checklist before claiming a task is 3 boundary patterns. Run this checklist before claiming a task is
done. done.
3. **`scripts/audit_exception_handling.py`** — the static analyzer 3. **`scripts/audit_exception_handling.py`** — the static analyzer
that catches violations before commit. The script classifies that catches violations before commit. The script classifies
`try/except/finally/raise` sites against 10 categories. Use it `try/except/finally/raise` sites against 10 categories. Use it
pre-commit. pre-commit.
4. **`scripts/audit_exception_handling.py --strict`** — the CI gate. 4. **`scripts/audit_exception_handling.py --strict`** — the CI gate.
Exits 1 on any violation. Wire this into pre-commit hooks and CI. Exits 1 on any violation. Wire this into pre-commit hooks and CI.
**The 4 enforcement audit scripts (the project-level enforcement set):** **The 4 enforcement audit scripts (the project-level enforcement set):**
+53 -53
View File
@@ -9,7 +9,7 @@ To serve as an expert-level utility for personal developer use on small projects
For deep implementation details when planning or implementing tracks, consult `docs/` (last refreshed: 2026-06-02 via the comprehensive documentation refresh track): For deep implementation details when planning or implementing tracks, consult `docs/` (last refreshed: 2026-06-02 via the comprehensive documentation refresh track):
**Core architecture:** **Core architecture:**
- **[docs/guide_architecture.md](../docs/guide_architecture.md):** Threading model, event system, AI client multi-provider (Gemini, Anthropic, DeepSeek, Gemini CLI, MiniMax), HITL mechanism, comms logging - **[docs/guide_architecture.md](../docs/guide_architecture.md):** Threading model, event system, AI client multi-provider (Gemini, Anthropic, DeepSeek, MiniMax), HITL mechanism, comms logging
- **[docs/guide_meta_boundary.md](../docs/guide_meta_boundary.md):** The critical distinction between the Application's Strict-HITL environment and the Meta-Tooling environment used to build it - **[docs/guide_meta_boundary.md](../docs/guide_meta_boundary.md):** The critical distinction between the Application's Strict-HITL environment and the Meta-Tooling environment used to build it
- **[docs/guide_tools.md](../docs/guide_tools.md):** MCP Bridge, 45-tool inventory, Hook API, ApiHookClient, shell runner - **[docs/guide_tools.md](../docs/guide_tools.md):** MCP Bridge, 45-tool inventory, Hook API, ApiHookClient, shell runner
- **[docs/guide_mma.md](../docs/guide_mma.md):** 4-tier orchestration, DAG engine, worker lifecycle, persona application - **[docs/guide_mma.md](../docs/guide_mma.md):** 4-tier orchestration, DAG engine, worker lifecycle, persona application
@@ -17,7 +17,7 @@ For deep implementation details when planning or implementing tracks, consult `d
**Per-source-file references (NEW):** **Per-source-file references (NEW):**
- **[docs/guide_gui_2.md](../docs/guide_gui_2.md):** `src/gui_2.py` (~437KB): App class lifecycle, ~90 module-level render functions, Multi-Viewport docks, panel registry, ImGuiScope context managers, hot reload support - **[docs/guide_gui_2.md](../docs/guide_gui_2.md):** `src/gui_2.py` (~437KB): App class lifecycle, ~90 module-level render functions, Multi-Viewport docks, panel registry, ImGuiScope context managers, hot reload support
- **[docs/guide_ai_client.md](../docs/guide_ai_client.md):** `src/ai_client.py` (~166KB): multi-provider LLM singleton (8 providers: gemini, anthropic, gemini_cli, deepseek, minimax, qwen, grok, llama), async dispatch via `asyncio.gather`, threading.local source tier tagging, Anthropic ephemeral + Gemini explicit caching, Tier 4 QA error interception, inlined `VendorCapabilities` registry (moved from the deleted `src/vendor_capabilities.py`) - **[docs/guide_ai_client.md](../docs/guide_ai_client.md):** `src/ai_client.py` (~166KB): multi-provider LLM singleton (7 providers: gemini, anthropic, deepseek, minimax, qwen, grok, llama), async dispatch via `asyncio.gather`, threading.local source tier tagging, Anthropic ephemeral + Gemini explicit caching, Tier 4 QA error interception, inlined `VendorCapabilities` registry (moved from the deleted `src/vendor_capabilities.py`)
- **[docs/guide_api_hooks.md](../docs/guide_api_hooks.md):** `src/api_hooks.py` + `src/api_hook_client.py` (~51KB + ~38KB): HookServer on `127.0.0.1:8999`, ApiHookClient wrapper, 8+ endpoints, Remote Confirmation Protocol via `/api/ask` - **[docs/guide_api_hooks.md](../docs/guide_api_hooks.md):** `src/api_hooks.py` + `src/api_hook_client.py` (~51KB + ~38KB): HookServer on `127.0.0.1:8999`, ApiHookClient wrapper, 8+ endpoints, Remote Confirmation Protocol via `/api/ask`
- **[docs/guide_mcp_client.md](../docs/guide_mcp_client.md):** `src/mcp_client.py` (~92KB, 45 tools): 3-layer security (Allowlist → Validate → Resolve), all native tools (File I/O, Python AST, C/C++ AST, Analysis, Network, Runtime, Beads), ExternalMCPManager (Stdio + SSE), JSON-RPC 2.0 engine. Tool specs now live in `src/mcp_tool_specs.py` (typed `ToolSpec` dataclass + `_REGISTRY`); `mcp_client.py` re-exports `TOOL_NAMES` for backward compat. - **[docs/guide_mcp_client.md](../docs/guide_mcp_client.md):** `src/mcp_client.py` (~92KB, 45 tools): 3-layer security (Allowlist → Validate → Resolve), all native tools (File I/O, Python AST, C/C++ AST, Analysis, Network, Runtime, Beads), ExternalMCPManager (Stdio + SSE), JSON-RPC 2.0 engine. Tool specs now live in `src/mcp_tool_specs.py` (typed `ToolSpec` dataclass + `_REGISTRY`); `mcp_client.py` re-exports `TOOL_NAMES` for backward compat.
- **[docs/guide_app_controller.md](../docs/guide_app_controller.md):** `src/app_controller.py` (~240KB): headless orchestrator, AppState dataclass, all subsystem managers, `_predefined_callbacks`/`_gettable_fields` Hook API registries, SyncEventQueue, headless mode - **[docs/guide_app_controller.md](../docs/guide_app_controller.md):** `src/app_controller.py` (~240KB): headless orchestrator, AppState dataclass, all subsystem managers, `_predefined_callbacks`/`_gettable_fields` Hook API registries, SyncEventQueue, headless mode
@@ -49,57 +49,57 @@ For deep implementation details when planning or implementing tracks, consult `d
## Key Features ## Key Features
- **Multi-Provider Integration:** Supports Gemini, Anthropic, DeepSeek, Gemini CLI (headless), MiniMax, Qwen, Grok, and Llama (Ollama) with seamless switching. - **Multi-Provider Integration:** Supports Gemini, Anthropic, DeepSeek, (headless), MiniMax, Qwen, Grok, and Llama (Ollama) with seamless switching.
- **4-Tier Hierarchical Multi-Model Architecture:** Orchestrates an intelligent cascade of specialized models to isolate cognitive loads and minimize token burn. - **4-Tier Hierarchical Multi-Model Architecture:** Orchestrates an intelligent cascade of specialized models to isolate cognitive loads and minimize token burn.
- **Tier 1 (Orchestrator):** Strategic product alignment, setup (`/conductor:setup`), and track initialization (`/conductor:newTrack`) using `gemini-3.1-pro-preview`. - **Tier 1 (Orchestrator):** Strategic product alignment, setup (`/conductor:setup`), and track initialization (`/conductor:newTrack`) using `gemini-3.1-pro-preview`.
- **Tier 2 (Tech Lead):** Technical oversight and track execution (`/conductor:implement`) using `gemini-3-flash-preview`. Maintains persistent context throughout implementation. - **Tier 2 (Tech Lead):** Technical oversight and track execution (`/conductor:implement`) using `gemini-3-flash-preview`. Maintains persistent context throughout implementation.
- **Tier 3 (Worker):** Surgical code implementation and TDD using `gemini-2.5-flash-lite`. Operates statelessly with tool access and dependency skeletons. - **Tier 3 (Worker):** Surgical code implementation and TDD using `gemini-2.5-flash-lite`. Operates statelessly with tool access and dependency skeletons.
- **Tier 4 (QA):** Error analysis and diagnostics using `gemini-2.5-flash-lite`. Operates statelessly with tool access. - **Tier 4 (QA):** Error analysis and diagnostics using `gemini-2.5-flash-lite`. Operates statelessly with tool access.
- **MMA Delegation Engine:** Route tasks, ensuring role-scoped context and detailed observability via timestamped sub-agent logs. Supports dynamic ticket creation and dependency resolution via an automated Dispatcher Loop. - **MMA Delegation Engine:** Route tasks, ensuring role-scoped context and detailed observability via timestamped sub-agent logs. Supports dynamic ticket creation and dependency resolution via an automated Dispatcher Loop.
- **MMA Observability Dashboard:** A high-density control center within the GUI for monitoring and managing the 4-Tier architecture. - **MMA Observability Dashboard:** A high-density control center within the GUI for monitoring and managing the 4-Tier architecture.
- **Track Browser:** Real-time visualization of all implementation tracks with status indicators and progress bars. Includes a dedicated **Active Track Summary** featuring a color-coded progress bar, precise ticket status breakdown (Completed, In Progress, Blocked, Todo), and dynamic **ETA estimation** based on historical completion times. - **Track Browser:** Real-time visualization of all implementation tracks with status indicators and progress bars. Includes a dedicated **Active Track Summary** featuring a color-coded progress bar, precise ticket status breakdown (Completed, In Progress, Blocked, Todo), and dynamic **ETA estimation** based on historical completion times.
- **Visual Task DAG:** An interactive, node-based visualizer for the active track's task dependencies using `imgui-node-editor`. Features color-coded state tracking (Ready, Running, Blocked, Done), drag-and-drop dependency creation, and right-click deletion. - **Visual Task DAG:** An interactive, node-based visualizer for the active track's task dependencies using `imgui-node-editor`. Features color-coded state tracking (Ready, Running, Blocked, Done), drag-and-drop dependency creation, and right-click deletion.
- **Strategy Visualization:** Dedicated real-time output streams for Tier 1 (Strategic Planning) and Tier 2/3 (Execution) agents, allowing the user to follow the agent's reasoning chains alongside the task DAG. - **Strategy Visualization:** Dedicated real-time output streams for Tier 1 (Strategic Planning) and Tier 2/3 (Execution) agents, allowing the user to follow the agent's reasoning chains alongside the task DAG.
- **Agent-Focused Filtering:** Allows the user to focus the entire GUI (Session Hub, Discussion Hub, Comms) on a specific agent's activities and scoped context. - **Agent-Focused Filtering:** Allows the user to focus the entire GUI (Session Hub, Discussion Hub, Comms) on a specific agent's activities and scoped context.
- **Track-Scoped State Management:** Segregates discussion history and task progress into per-track state files. Supports **Project-Specific Conductor Directories**, defaulting to `./conductor` relative to each project's TOML file. Projects can define their own conductor path override in `manual_slop.toml` (`[conductor].dir`) via the Projects tab for isolated track management. This prevents global context pollution and ensures the Tech Lead session is isolated to the specific track's objective. - **Track-Scoped State Management:** Segregates discussion history and task progress into per-track state files. Supports **Project-Specific Conductor Directories**, defaulting to `./conductor` relative to each project's TOML file. Projects can define their own conductor path override in `manual_slop.toml` (`[conductor].dir`) via the Projects tab for isolated track management. This prevents global context pollution and ensures the Tech Lead session is isolated to the specific track's objective.
**Native DAG Execution Engine:** Employs a Python-based Directed Acyclic Graph (DAG) engine to manage complex task dependencies. Supports automated topological sorting, robust cycle detection, and **transitive blocking propagation** (cascading `blocked` status to downstream dependents to prevent execution stalls). **Native DAG Execution Engine:** Employs a Python-based Directed Acyclic Graph (DAG) engine to manage complex task dependencies. Supports automated topological sorting, robust cycle detection, and **transitive blocking propagation** (cascading `blocked` status to downstream dependents to prevent execution stalls).
- **Programmable Execution State machine:** Governing the transition between "Auto-Queue" (autonomous worker spawning) and "Step Mode" (explicit manual approval for each task transition). - **Programmable Execution State machine:** Governing the transition between "Auto-Queue" (autonomous worker spawning) and "Step Mode" (explicit manual approval for each task transition).
- **Role-Scoped Documentation:** Automated mapping of foundational documents to specific tiers to prevent token bloat and maintain high-signal context. - **Role-Scoped Documentation:** Automated mapping of foundational documents to specific tiers to prevent token bloat and maintain high-signal context.
- **Tiered Context Scoping:** Employs optimized context subsets for each tier. Tiers 1 & 2 receive strategic documents and full history, while Tier 3/4 workers receive task-specific "Focus Files" and automated AST dependency skeletons. - **Tiered Context Scoping:** Employs optimized context subsets for each tier. Tiers 1 & 2 receive strategic documents and full history, while Tier 3/4 workers receive task-specific "Focus Files" and automated AST dependency skeletons.
- **Worker Spawn Interceptor:** A mandatory security gate that intercepts every sub-agent launch. Provides a GUI modal allowing the user to review, modify, or reject the worker's prompt and file context before it is sent to the API. - **Worker Spawn Interceptor:** A mandatory security gate that intercepts every sub-agent launch. Provides a GUI modal allowing the user to review, modify, or reject the worker's prompt and file context before it is sent to the API.
- **Strict Memory Siloing:** Employs tree-sitter AST-based interface extraction (Skeleton View, Curated View, and Targeted View) and "Context Amnesia" to provide workers only with the absolute minimum context required. Supports **Python, C, and C++** languages for structural extraction. Features an intelligent context aggregation engine utilizing **Hash-Based Caching (SHA256)** and LRU eviction to eliminate redundant processing. Employs **Tier-Level Aggregation Strategies** (`full`, `summarize`, `skeleton`) configured directly via Agent Personas, integrating high-tier AI sub-agents during the aggregation pass to generate succinct, high-signal summaries for both code and text files. Includes **Manual Skeleton Context Injection**, allowing developers to preview and manually inject file skeletons or full content into discussions via a dedicated GUI modal. Features multi-level dependency traversal and AST caching to minimize re-parsing overhead and token burn. - **Strict Memory Siloing:** Employs tree-sitter AST-based interface extraction (Skeleton View, Curated View, and Targeted View) and "Context Amnesia" to provide workers only with the absolute minimum context required. Supports **Python, C, and C++** languages for structural extraction. Features an intelligent context aggregation engine utilizing **Hash-Based Caching (SHA256)** and LRU eviction to eliminate redundant processing. Employs **Tier-Level Aggregation Strategies** (`full`, `summarize`, `skeleton`) configured directly via Agent Personas, integrating high-tier AI sub-agents during the aggregation pass to generate succinct, high-signal summaries for both code and text files. Includes **Manual Skeleton Context Injection**, allowing developers to preview and manually inject file skeletons or full content into discussions via a dedicated GUI modal. Features multi-level dependency traversal and AST caching to minimize re-parsing overhead and token burn.
- **Explicit Execution Control:** All AI-generated PowerShell scripts require explicit human confirmation via interactive UI dialogs before execution, supported by a global "Linear Execution Clutch" for deterministic debugging. - **Explicit Execution Control:** All AI-generated PowerShell scripts require explicit human confirmation via interactive UI dialogs before execution, supported by a global "Linear Execution Clutch" for deterministic debugging.
- **Parallel Multi-Agent Execution:** Executes multiple AI workers in parallel using a non-blocking execution engine and a dedicated `WorkerPool`. Features configurable concurrency limits (defaulting to 4) to optimize resource usage and prevent API rate limiting. - **Parallel Multi-Agent Execution:** Executes multiple AI workers in parallel using a non-blocking execution engine and a dedicated `WorkerPool`. Features configurable concurrency limits (defaulting to 4) to optimize resource usage and prevent API rate limiting.
- **Beads Mode Integration:** Supports [Beads](https://github.com/steveyegge/beads) as a first-class, project-specific alternative to markdown-based tracking. - **Beads Mode Integration:** Supports [Beads](https://github.com/steveyegge/beads) as a first-class, project-specific alternative to markdown-based tracking.
- **Git-Backed Issue Tracking:** Uses a local `.beads` repository (backed by Dolt) to store the task graph, allowing tracks and tickets to be versioned alongside the code. - **Git-Backed Issue Tracking:** Uses a local `.beads` repository (backed by Dolt) to store the task graph, allowing tracks and tickets to be versioned alongside the code.
- **Beads Toolset:** Provides a suite of MCP tools (`bd_create`, `bd_update`, `bd_ready`, `bd_list`) for agents to manage the issue graph autonomously. - **Beads Toolset:** Provides a suite of MCP tools (`bd_create`, `bd_update`, `bd_ready`, `bd_list`) for agents to manage the issue graph autonomously.
- **Context Compaction:** Automatically summarizes completed beads to preserve context window space for the active task. - **Context Compaction:** Automatically summarizes completed beads to preserve context window space for the active task.
- **Augmented Visualizations:** Integrates with the Visual DAG and MMA Dashboard to provide real-time visibility into the Dolt-backed issue graph. - **Augmented Visualizations:** Integrates with the Visual DAG and MMA Dashboard to provide real-time visibility into the Dolt-backed issue graph.
- **Parallel Tool Execution:** Executes independent tool calls (e.g., parallel file reads) concurrently within a single agent turn using an asynchronous execution engine, significantly reducing end-to-end latency. - **Parallel Tool Execution:** Executes independent tool calls (e.g., parallel file reads) concurrently within a single agent turn using an asynchronous execution engine, significantly reducing end-to-end latency.
- **Automated Tier 4 QA:** Integrates real-time error interception in the shell runner, automatically forwarding technical failures to cheap sub-agents for 20-word diagnostic summaries injected back into the worker history. - **Automated Tier 4 QA:** Integrates real-time error interception in the shell runner, automatically forwarding technical failures to cheap sub-agents for 20-word diagnostic summaries injected back into the worker history.
- **External MCP Server Support:** Adds support for integrating external Model Context Protocol (MCP) servers, expanding the agent's toolset with the broader MCP ecosystem. - **External MCP Server Support:** Adds support for integrating external Model Context Protocol (MCP) servers, expanding the agent's toolset with the broader MCP ecosystem.
- **Multi-Server Lifecycle Management:** Orchestrates multiple concurrent MCP server sessions (Stdio for local subprocesses and SSE for remote servers). - **Multi-Server Lifecycle Management:** Orchestrates multiple concurrent MCP server sessions (Stdio for local subprocesses and SSE for remote servers).
- **Flexible Configuration:** Supports global (`config.toml`) and project-specific (`manual_slop.toml`) paths for `mcp_config.json` (standard MCP configuration format). - **Flexible Configuration:** Supports global (`config.toml`) and project-specific (`manual_slop.toml`) paths for `mcp_config.json` (standard MCP configuration format).
- **Auto-Start & Discovery:** Automatically initializes configured servers on project load and dynamically aggregates their tools into the agent's capability declarations. - **Auto-Start & Discovery:** Automatically initializes configured servers on project load and dynamically aggregates their tools into the agent's capability declarations.
- **Dedicated Operations UI:** Features a new **External Tools** section within the Operations Hub for monitoring server status (idle, starting, running, error) and browsing discovered tool schemas. Supports **Pop-Out Panel functionality**, allowing the External Tools interface to be detached into a standalone window for optimized multi-monitor workflows. - **Dedicated Operations UI:** Features a new **External Tools** section within the Operations Hub for monitoring server status (idle, starting, running, error) and browsing discovered tool schemas. Supports **Pop-Out Panel functionality**, allowing the External Tools interface to be detached into a standalone window for optimized multi-monitor workflows.
- **Strict HITL Safety:** All external tool calls are intercepted and require explicit human-in-the-loop approval via the standard confirmation dialog before execution. - **Strict HITL Safety:** All external tool calls are intercepted and require explicit human-in-the-loop approval via the standard confirmation dialog before execution.
- **Retrieval-Augmented Generation (RAG) Support:** Introduces advanced retrieval capabilities to overcome context window limitations and reduce hallucination. - **Retrieval-Augmented Generation (RAG) Support:** Introduces advanced retrieval capabilities to overcome context window limitations and reduce hallucination.
- **Multi-Source Retrieval:** Supports local vector stores (ChromaDB) and an **External RAG Bridge** via the Model Context Protocol (MCP) for connecting to third-party retrieval services. - **Multi-Source Retrieval:** Supports local vector stores (ChromaDB) and an **External RAG Bridge** via the Model Context Protocol (MCP) for connecting to third-party retrieval services.
- **High-Performance Indexing:** Employs a parallelized indexing pipeline using `ThreadPoolExecutor` and incremental updates based on file `mtime` to handle large codebases efficiently. - **High-Performance Indexing:** Employs a parallelized indexing pipeline using `ThreadPoolExecutor` and incremental updates based on file `mtime` to handle large codebases efficiently.
- **Deep Discussion Integration:** Retrieved context fragments are automatically prepended to agent prompts and captured in the discussion history, featuring a dedicated visualization mode with source buttons for instant file navigation. - **Deep Discussion Integration:** Retrieved context fragments are automatically prepended to agent prompts and captured in the discussion history, featuring a dedicated visualization mode with source buttons for instant file navigation.
- **Configurable Strategy:** Users can toggle RAG globally and fine-tune retrieval parameters (source, embedding provider, chunk size/overlap) directly within the AI Settings. - **Configurable Strategy:** Users can toggle RAG globally and fine-tune retrieval parameters (source, embedding provider, chunk size/overlap) directly within the AI Settings.
- **Automated Synchronization:** Features background re-indexing of the project workspace, ensuring the vector store remains consistent with the current project state. - **Automated Synchronization:** Features background re-indexing of the project workspace, ensuring the vector store remains consistent with the current project state.
- **Undo/Redo History Support:** Implements a robust, non-provider based undo/redo system for managing UI state and discussion mutations. - **Undo/Redo History Support:** Implements a robust, non-provider based undo/redo system for managing UI state and discussion mutations.
- **Comprehensive State Snapshots:** Captures all critical UI state, including text inputs (system prompts, AI input), model parameters (Temperature, Top-P), and context management (files, screenshots). - **Comprehensive State Snapshots:** Captures all critical UI state, including text inputs (system prompts, AI input), model parameters (Temperature, Top-P), and context management (files, screenshots).
- **Discussion Mutation Tracking:** Allows reverting and redoing additions, deletions, and structural changes to the discussion history. - **Discussion Mutation Tracking:** Allows reverting and redoing additions, deletions, and structural changes to the discussion history.
- **History List View:** Features a dedicated, scrollable panel showing recent actions with timestamps, allowing users to jump directly to any historical state. - **History List View:** Features a dedicated, scrollable panel showing recent actions with timestamps, allowing users to jump directly to any historical state.
- **Tactile Hotkeys:** Supports industry-standard shortcuts (`Ctrl+Z`, `Ctrl+Y`, `Ctrl+Shift+Z`) for fast, intuitive state navigation. - **Tactile Hotkeys:** Supports industry-standard shortcuts (`Ctrl+Z`, `Ctrl+Y`, `Ctrl+Shift+Z`) for fast, intuitive state navigation.
- **High-Fidelity Selectable UI:** Most read-only labels and logs across the interface (including discussion history, comms payloads, tool outputs, and telemetry metrics) are now implemented as selectable text fields. This enables standard OS-level text selection and copying (Ctrl+C) while maintaining a high-density, non-editable aesthetic. - **High-Fidelity Selectable UI:** Most read-only labels and logs across the interface (including discussion history, comms payloads, tool outputs, and telemetry metrics) are now implemented as selectable text fields. This enables standard OS-level text selection and copying (Ctrl+C) while maintaining a high-density, non-editable aesthetic.
- **High-Fidelity UI Rendering:** Employs advanced 3x font oversampling and sub-pixel positioning to ensure crisp, high-clarity text rendering across all resolutions, enhancing readability for dense logs and complex code fragments. - **High-Fidelity UI Rendering:** Employs advanced 3x font oversampling and sub-pixel positioning to ensure crisp, high-clarity text rendering across all resolutions, enhancing readability for dense logs and complex code fragments.
- **Workspace Docking & Layout Profiles:** Expands layout management to support named workspace profiles, capturing multi-viewport docking arrangements, window visibility, and internal panel states. - **Workspace Docking & Layout Profiles:** Expands layout management to support named workspace profiles, capturing multi-viewport docking arrangements, window visibility, and internal panel states.
- **Scope Inheritance:** Profiles follow a Global and Project inheritance model, allowing for both universal defaults and project-specific layouts. - **Scope Inheritance:** Profiles follow a Global and Project inheritance model, allowing for both universal defaults and project-specific layouts.
- **Contextual Auto-Switch (Experimental):** An opt-in mechanism that automatically binds and loads specific workspace profiles based on the active MMA Tier or task context, dynamically reshaping the UI for the current cognitive load. - **Contextual Auto-Switch (Experimental):** An opt-in mechanism that automatically binds and loads specific workspace profiles based on the active MMA Tier or task context, dynamically reshaping the UI for the current cognitive load.
- **Enhanced MMA Observability:** Worker streams and ticket previews now support direct text selection, allowing for easy extraction of specific logs or reasoning fragments during parallel execution. - **Enhanced MMA Observability:** Worker streams and ticket previews now support direct text selection, allowing for easy extraction of specific logs or reasoning fragments during parallel execution.
- **Transparent Context Visibility:** A dedicated **Session Hub** exposes the exact aggregated markdown and resolved system prompt sent to the AI. - **Transparent Context Visibility:** A dedicated **Session Hub** exposes the exact aggregated markdown and resolved system prompt sent to the AI.
- **Injection Timeline:** Discussion history visually indicates the precise moments when files or screenshots were injected into the session context. - **Injection Timeline:** Discussion history visually indicates the precise moments when files or screenshots were injected into the session context.
@@ -124,18 +124,18 @@ For deep implementation details when planning or implementing tracks, consult `d
- **Context & Token Visualization:** Detailed UI panels for monitoring real-time token usage, history depth, and **visual cache awareness** (tracking specific files currently live in the provider's context cache). - **Context & Token Visualization:** Detailed UI panels for monitoring real-time token usage, history depth, and **visual cache awareness** (tracking specific files currently live in the provider's context cache).
- **On-Demand Definition Lookup:** Allows developers to request specific class or function definitions during discussions using `@SymbolName` syntax. Injected definitions feature syntax highlighting, intelligent collapsing for long blocks, and a **[Source]** button for instant navigation to the full file. - **On-Demand Definition Lookup:** Allows developers to request specific class or function definitions during discussions using `@SymbolName` syntax. Injected definitions feature syntax highlighting, intelligent collapsing for long blocks, and a **[Source]** button for instant navigation to the full file.
- **Manual Ticket Queue Management:** Provides a dedicated GUI panel for granular control over the implementation queue. Features include color-coded priority assignment (High, Medium, Low), multi-select bulk operations (Execute, Skip, Block), and interactive drag-and-drop reordering with real-time Directed Acyclic Graph (DAG) validation. - **Manual Ticket Queue Management:** Provides a dedicated GUI panel for granular control over the implementation queue. Features include color-coded priority assignment (High, Medium, Low), multi-select bulk operations (Execute, Skip, Block), and interactive drag-and-drop reordering with real-time Directed Acyclic Graph (DAG) validation.
- **System Prompt Presets:** Comprehensive management system for saving and switching between complex system prompt configurations. Features full visibility and customization of the **Foundational Base System Prompt**, allowing users to modify the core instructions that define agent capabilities and tool usage heuristics. - **Scoped Inheritance:** Supports **Global** (application-wide) and **Project-Specific** presets. Project presets with the same name automatically override global counterparts, allowing for fine-tuned context tailoring. - **System Prompt Presets:** Comprehensive management system for saving and switching between complex system prompt configurations. Features full visibility and customization of the **Foundational Base System Prompt**, allowing users to modify the core instructions that define agent capabilities and tool usage heuristics. - **Scoped Inheritance:** Supports **Global** (application-wide) and **Project-Specific** presets. Project presets with the same name automatically override global counterparts, allowing for fine-tuned context tailoring.
- **Command Palette:** A global, keyboard-driven launcher for actions across Manual Slop, triggered by `Ctrl+Shift+P`. Provides fuzzy-search across all built-in and user-defined commands. Includes an **"Everything" mode** (`Ctrl+Shift+E`) that searches across commands, files, symbols, history, and settings. Uses an **async context preview worker** to prevent UI hangs during cross-domain searches. See [guide_command_palette.md](../docs/guide_command_palette.md). - **Command Palette:** A global, keyboard-driven launcher for actions across Manual Slop, triggered by `Ctrl+Shift+P`. Provides fuzzy-search across all built-in and user-defined commands. Includes an **"Everything" mode** (`Ctrl+Shift+E`) that searches across commands, files, symbols, history, and settings. Uses an **async context preview worker** to prevent UI hangs during cross-domain searches. See [guide_command_palette.md](../docs/guide_command_palette.md).
- **Full AI Profiles:** Presets capture not only the system prompt text but also critical model parameters like **Temperature**, **Top-P**, and **Max Output Tokens**. - **Full AI Profiles:** Presets capture not only the system prompt text but also critical model parameters like **Temperature**, **Top-P**, and **Max Output Tokens**.
- **Preset Manager Modal:** A dedicated high-density GUI for creating, editing, and deleting presets with real-time validation and instant application to the active session. - **Preset Manager Modal:** A dedicated high-density GUI for creating, editing, and deleting presets with real-time validation and instant application to the active session.
- **Agent Personas & Unified Profiles:** Consolidates model settings, provider routing, system prompts, tool presets, and bias profiles into named "Persona" entities. - **Agent Personas & Unified Profiles:** Consolidates model settings, provider routing, system prompts, tool presets, and bias profiles into named "Persona" entities.
- **Single Configuration Entity:** Switch models, tool weights, and system prompts simultaneously using a single Persona selection. - **Single Configuration Entity:** Switch models, tool weights, and system prompts simultaneously using a single Persona selection.
- **Persona Editor Modal:** A dedicated high-density GUI for creating, editing, and deleting Personas. - **Persona Editor Modal:** A dedicated high-density GUI for creating, editing, and deleting Personas.
- **MMA Granular Assignment:** Allows assigning specific Personas to individual agents within the 4-Tier Hierarchical MMA. - **MMA Granular Assignment:** Allows assigning specific Personas to individual agents within the 4-Tier Hierarchical MMA.
- **Agent Tool Weighting & Bias:** Influences agent tool selection via a weighting system. - **Agent Tool Weighting & Bias:** Influences agent tool selection via a weighting system.
- **Semantic Nudging:** Automatically prefixes tool and parameter descriptions with priority tags (e.g., [HIGH PRIORITY], [PREFERRED]) to bias model selection. - **Semantic Nudging:** Automatically prefixes tool and parameter descriptions with priority tags (e.g., [HIGH PRIORITY], [PREFERRED]) to bias model selection.
- **Dynamic Tooling Strategy:** Automatically appends a Markdown "Tooling Strategy" section to system instructions based on the active preset and global bias profile. - **Dynamic Tooling Strategy:** Automatically appends a Markdown "Tooling Strategy" section to system instructions based on the active preset and global bias profile.
- **Global Bias Profiles:** Application of category-level multipliers (e.g., Execution-Focused, Discovery-Heavy) to influence agent behavior across broad toolsets. - **Global Bias Profiles:** Application of category-level multipliers (e.g., Execution-Focused, Discovery-Heavy) to influence agent behavior across broad toolsets.
- **Priority Badges & Refined Layout:** High-density, color-coded visual indicators in tool lists showing the assigned priority level of each capability. Displays tool names before radio buttons with consistent spacing for improved readability. - **Priority Badges & Refined Layout:** High-density, color-coded visual indicators in tool lists showing the assigned priority level of each capability. Displays tool names before radio buttons with consistent spacing for improved readability.
- **Category-Based Filtering:** Integrated category filtering in both the Active Tools panel and the Tool Preset Manager, allowing users to quickly manage large toolsets. - **Category-Based Filtering:** Integrated category filtering in both the Active Tools panel and the Tool Preset Manager, allowing users to quickly manage large toolsets.
- **Fine-Grained Weight Control:** Integrated sliders in the Preset Manager for adjusting individual tool weights (1-5) and parameter-level biases. - **Fine-Grained Weight Control:** Integrated sliders in the Preset Manager for adjusting individual tool weights (1-5) and parameter-level biases.
+17 -17
View File
@@ -1,4 +1,4 @@
# Technology Stack: Manual Slop # Technology Stack: Manual Slop
> **Core Value (added 2026-06-25):** C11/Odin/Jai semantics in this Python runtime. See `conductor/product-guidelines.md` "Core Value", `conductor/code_styleguides/data_oriented_design.md` §8.5, and `conductor/code_styleguides/python.md` §17. Banned: `dict[str, Any]`, `Any`, `Optional[T]`, `hasattr()` for entity dispatch, `.get()` on known fields. Use typed `@dataclass(frozen=True, slots=True)` with explicit fields. Use `Result[T]` + `NIL_T` sentinels. > **Core Value (added 2026-06-25):** C11/Odin/Jai semantics in this Python runtime. See `conductor/product-guidelines.md` "Core Value", `conductor/code_styleguides/data_oriented_design.md` §8.5, and `conductor/code_styleguides/python.md` §17. Banned: `dict[str, Any]`, `Any`, `Optional[T]`, `hasattr()` for entity dispatch, `.get()` on known fields. Use typed `@dataclass(frozen=True, slots=True)` with explicit fields. Use `Result[T]` + `NIL_T` sentinels.
@@ -46,19 +46,19 @@
- **src/tool_presets.py:** Extends `ToolPresetManager` to handle nested `Tool` models, weights, and global `BiasProfile` persistence within `tool_presets.toml`. - **src/tool_presets.py:** Extends `ToolPresetManager` to handle nested `Tool` models, weights, and global `BiasProfile` persistence within `tool_presets.toml`.
- **src/mcp_client.py:** Implements the native tool dispatch (45 tools) and the `ExternalMCPManager` for orchestrating third-party Model Context Protocol servers. The typed `ToolSpec` registry now lives in `src/mcp_tool_specs.py` (`ToolSpec` dataclass + `_REGISTRY` + `tool_names()`); `mcp_client.py` re-exports `TOOL_NAMES = mcp_tool_specs.tool_names()` for backward compat. See [docs/guide_mcp_client.md](../docs/guide_mcp_client.md) for the complete 3-layer security model (Allowlist → Validate → Resolve) and tool inventory. - **src/mcp_client.py:** Implements the native tool dispatch (45 tools) and the `ExternalMCPManager` for orchestrating third-party Model Context Protocol servers. The typed `ToolSpec` registry now lives in `src/mcp_tool_specs.py` (`ToolSpec` dataclass + `_REGISTRY` + `tool_names()`); `mcp_client.py` re-exports `TOOL_NAMES = mcp_tool_specs.tool_names()` for backward compat. See [docs/guide_mcp_client.md](../docs/guide_mcp_client.md) for the complete 3-layer security model (Allowlist → Validate → Resolve) and tool inventory.
- **StdioMCPServer:** Manages local MCP servers via asynchronous subprocess pipes (stdin/stdout/stderr). - **StdioMCPServer:** Manages local MCP servers via asynchronous subprocess pipes (stdin/stdout/stderr).
- **RemoteMCPServer (SSE):** Provides a foundation for remote MCP integration via Server-Sent Events. - **RemoteMCPServer (SSE):** Provides a foundation for remote MCP integration via Server-Sent Events.
- **JSON-RPC 2.0 Engine:** Handles asynchronous message routing, request/response matching, and error handling for all external MCP communication. - **JSON-RPC 2.0 Engine:** Handles asynchronous message routing, request/response matching, and error handling for all external MCP communication.
- **AST-Based C/C++ Tools:** Provides `ts_c_get_skeleton`, `ts_cpp_get_skeleton`, `ts_c_get_code_outline`, and `ts_cpp_get_code_outline` for structural analysis of C/C++ codebases using tree-sitter. - **AST-Based C/C++ Tools:** Provides `ts_c_get_skeleton`, `ts_cpp_get_skeleton`, `ts_c_get_code_outline`, and `ts_cpp_get_code_outline` for structural analysis of C/C++ codebases using tree-sitter.
- **AST-Based Python Tools (15):** `py_get_skeleton`, `py_get_code_outline`, `py_get_definition`, `py_update_definition`, `py_get_signature`, `py_set_signature`, `py_get_class_summary`, `py_get_var_declaration`, `py_set_var_declaration`, `py_get_hierarchy`, `py_get_docstring`, `py_get_imports`, `py_find_usages`, `py_check_syntax`, plus structural mutators `py_remove_def`, `py_add_def`, `py_move_def`, `py_region_wrap`. - **AST-Based Python Tools (15):** `py_get_skeleton`, `py_get_code_outline`, `py_get_definition`, `py_update_definition`, `py_get_signature`, `py_set_signature`, `py_get_class_summary`, `py_get_var_declaration`, `py_set_var_declaration`, `py_get_hierarchy`, `py_get_docstring`, `py_get_imports`, `py_find_usages`, `py_check_syntax`, plus structural mutators `py_remove_def`, `py_add_def`, `py_move_def`, `py_region_wrap`.
- **Network Tools:** `web_search` (DuckDuckGo HTML scrape), `fetch_url` (HTML → text). - **Network Tools:** `web_search` (DuckDuckGo HTML scrape), `fetch_url` (HTML → text).
- **Beads Tools (4):** `bd_list`, `bd_create`, `bd_update`, `bd_ready` — interface to the Beads/Dolt backend. - **Beads Tools (4):** `bd_list`, `bd_create`, `bd_update`, `bd_ready` — interface to the Beads/Dolt backend.
- **src/api_hooks.py + src/api_hook_client.py:** Implements the Hook API and Python client wrapper for external automation. See [docs/guide_api_hooks.md](../docs/guide_api_hooks.md). - **src/api_hooks.py + src/api_hook_client.py:** Implements the Hook API and Python client wrapper for external automation. See [docs/guide_api_hooks.md](../docs/guide_api_hooks.md).
- **HookServer:** FastAPI/Uvicorn server on `127.0.0.1:8999`, started by `AppController` when `--enable-test-hooks` is set. Exposes 8+ REST endpoints (`/status`, `/api/gui`, `/api/ask`, `/api/gui/mma_status`, `/api/performance`, `/api/comms`, `/api/diagnostics`). - **HookServer:** FastAPI/Uvicorn server on `127.0.0.1:8999`, started by `AppController` when `--enable-test-hooks` is set. Exposes 8+ REST endpoints (`/status`, `/api/gui`, `/api/ask`, `/api/gui/mma_status`, `/api/performance`, `/api/comms`, `/api/diagnostics`).
- **ApiHookClient:** Python client with retry logic, health-check polling, and timeout configuration. Used by all `live_gui` tests, the WorkerPool, and external scripts. - **ApiHookClient:** Python client with retry logic, health-check polling, and timeout configuration. Used by all `live_gui` tests, the WorkerPool, and external scripts.
- **`/api/ask` Protocol:** Non-blocking, ID-based challenge/response for synchronous HITL approvals from external contexts. - **`/api/ask` Protocol:** Non-blocking, ID-based challenge/response for synchronous HITL approvals from external contexts.
- **`_predefined_callbacks` and `_gettable_fields`:** AppController-owned registries that the Hook API consumes to expose any App method as a `custom_callback` action. - **`_predefined_callbacks` and `_gettable_fields`:** AppController-owned registries that the Hook API consumes to expose any App method as a `custom_callback` action.
- **src/rag_engine.py:** Core RAG implementation managing the vector store lifecycle, chunking strategies (character-based and AST-aware), and multi-provider search. Integrates with **ChromaDB** for local persistence, uses external embeddings by default, and provides an optional local embedding path via `manual_slop[local-rag]`. - **src/rag_engine.py:** Core RAG implementation managing the vector store lifecycle, chunking strategies (character-based and AST-aware), and multi-provider search. Integrates with **ChromaDB** for local persistence, uses external embeddings by default, and provides an optional local embedding path via `manual_slop[local-rag]`.
@@ -108,16 +108,16 @@
- **Manual Hot-Reload Pipeline:** Implements a `HotReloader` utility that manages module invalidation and state preservation, triggered by keyboard shortcuts (Ctrl+Alt+R) or GUI controls. - **Manual Hot-Reload Pipeline:** Implements a `HotReloader` utility that manages module invalidation and state preservation, triggered by keyboard shortcuts (Ctrl+Alt+R) or GUI controls.
- **src/command_palette.py + src/commands.py:** Implements the keyboard-driven Command Palette (Ctrl+Shift+P). See [docs/guide_command_palette.md](../docs/guide_command_palette.md) and [docs/guide_gui_2.md](../docs/guide_gui_2.md#command-palette). - **src/command_palette.py + src/commands.py:** Implements the keyboard-driven Command Palette (Ctrl+Shift+P). See [docs/guide_command_palette.md](../docs/guide_command_palette.md) and [docs/guide_gui_2.md](../docs/guide_gui_2.md#command-palette).
- **CommandRegistry:** Decorator-based command registration (`@registry.register`). 32+ built-in commands including `_toggle_command_palette`, `_open_command_palette`, theme switching, view presets, persona application. - **CommandRegistry:** Decorator-based command registration (`@registry.register`). 32+ built-in commands including `_toggle_command_palette`, `_open_command_palette`, theme switching, view presets, persona application.
- **fuzzy_match:** Subsequence matching with score (consecutive bonus, start-of-word bonus, length penalty). - **fuzzy_match:** Subsequence matching with score (consecutive bonus, start-of-word bonus, length penalty).
- **render_palette_modal:** Centered popup with input field, keyboard navigation (Up/Down/Enter/Esc), and live result filtering. - **render_palette_modal:** Centered popup with input field, keyboard navigation (Up/Down/Enter/Esc), and live result filtering.
- **defensive try/except wrapping:** All action callbacks wrapped to prevent GUI crashes from buggy commands. - **defensive try/except wrapping:** All action callbacks wrapped to prevent GUI crashes from buggy commands.
## Per-Source-File Deep Dives ## Per-Source-File Deep Dives
For the largest source files, consult the dedicated guides in `docs/`: For the largest source files, consult the dedicated guides in `docs/`:
- **[docs/guide_gui_2.md](../docs/guide_gui_2.md)** — `src/gui_2.py` (~437KB main GUI) - **[docs/guide_gui_2.md](../docs/guide_gui_2.md)** — `src/gui_2.py` (~437KB main GUI)
- **[docs/guide_ai_client.md](../docs/guide_ai_client.md)** — `src/ai_client.py` (~166KB multi-provider LLM, 8 providers; inlined `VendorCapabilities` registry) - **[docs/guide_ai_client.md](../docs/guide_ai_client.md)** — `src/ai_client.py` (~166KB multi-provider LLM, 7 providers; inlined `VendorCapabilities` registry)
- **[docs/guide_api_hooks.md](../docs/guide_api_hooks.md)** — `src/api_hooks.py` + `src/api_hook_client.py` (~51KB + ~38KB Hook API) - **[docs/guide_api_hooks.md](../docs/guide_api_hooks.md)** — `src/api_hooks.py` + `src/api_hook_client.py` (~51KB + ~38KB Hook API)
- **[docs/guide_mcp_client.md](../docs/guide_mcp_client.md)** — `src/mcp_client.py` (~92KB, 45 tools; tool specs live in `src/mcp_tool_specs.py`) - **[docs/guide_mcp_client.md](../docs/guide_mcp_client.md)** — `src/mcp_client.py` (~92KB, 45 tools; tool specs live in `src/mcp_tool_specs.py`)
- **[docs/guide_app_controller.md](../docs/guide_app_controller.md)** — `src/app_controller.py` (~240KB headless controller) - **[docs/guide_app_controller.md](../docs/guide_app_controller.md)** — `src/app_controller.py` (~240KB headless controller)
+198 -198
View File
@@ -42,10 +42,10 @@ Or use Python subprocess with `newline=''` to preserve line endings:
```python ```python
python -c " python -c "
with open('file.py', 'r', encoding='utf-8', newline='') as f: with open('file.py', 'r', encoding='utf-8', newline='') as f:
content = f.read() content = f.read()
content = content.replace(old, new) content = content.replace(old, new)
with open('file.py', 'w', encoding='utf-8', newline='') as f: with open('file.py', 'w', encoding='utf-8', newline='') as f:
f.write(content) f.write(content)
" "
``` ```
@@ -61,19 +61,19 @@ with open('file.py', 'w', encoding='utf-8', newline='') as f:
8. **File Naming Convention (HARD RULE, added 2026-06-11):** New `src/<thing>.py` files may only be created on the user's explicit request. Helpers and sub-systems go in the parent module. E.g., AI-client-specific code goes in `src/ai_client.py`; MCP-client code goes in `src/mcp_client.py`. If you find yourself about to create a new `src/<thing>.py` file, ASK FIRST. See `AGENTS.md` "File Size and Naming Convention" for the full rule. 8. **File Naming Convention (HARD RULE, added 2026-06-11):** New `src/<thing>.py` files may only be created on the user's explicit request. Helpers and sub-systems go in the parent module. E.g., AI-client-specific code goes in `src/ai_client.py`; MCP-client code goes in `src/mcp_client.py`. If you find yourself about to create a new `src/<thing>.py` file, ASK FIRST. See `AGENTS.md` "File Size and Naming Convention" for the full rule.
8. **Mandatory Research-First Protocol:** Before reading the full content of any file over 50 lines, you MUST use `get_file_summary`, `py_get_skeleton`, `py_get_code_outline`, or `py_get_docstring` to map the architecture and identify specific target ranges. Use `get_git_diff` to understand recent changes. Use `py_find_usages` to locate where symbols are used. 8. **Mandatory Research-First Protocol:** Before reading the full content of any file over 50 lines, you MUST use `get_file_summary`, `py_get_skeleton`, `py_get_code_outline`, or `py_get_docstring` to map the architecture and identify specific target ranges. Use `get_git_diff` to understand recent changes. Use `py_find_usages` to locate where symbols are used.
9. **Architecture Documentation Fallback:** When uncertain about threading, event flow, data structures, or module interactions, consult the deep-dive docs in `docs/` (last refreshed: 2026-06-02 via the comprehensive documentation refresh track, **8 new guides added**): 9. **Architecture Documentation Fallback:** When uncertain about threading, event flow, data structures, or module interactions, consult the deep-dive docs in `docs/` (last refreshed: 2026-06-02 via the comprehensive documentation refresh track, **8 new guides added**):
- **[docs/guide_architecture.md](../docs/guide_architecture.md):** Thread domains, cross-thread patterns, AI client multi-provider (Gemini, Anthropic, DeepSeek, Gemini CLI, MiniMax), HITL Execution Clutch. - **[docs/guide_architecture.md](../docs/guide_architecture.md):** Thread domains, cross-thread patterns, AI client multi-provider (Gemini, Anthropic, DeepSeek, MiniMax), HITL Execution Clutch.
- **[docs/guide_tools.md](../docs/guide_tools.md):** MCP Bridge 3-layer security, full 45-tool inventory, Hook API, ApiHookClient, `/api/ask` HITL protocol. - **[docs/guide_tools.md](../docs/guide_tools.md):** MCP Bridge 3-layer security, full 45-tool inventory, Hook API, ApiHookClient, `/api/ask` HITL protocol.
- **[docs/guide_mma.md](../docs/guide_mma.md):** Ticket/Track/WorkerContext data structures, DAG engine, ConductorEngine, Tier 2/3/4 lifecycles, persona application. - **[docs/guide_mma.md](../docs/guide_mma.md):** Ticket/Track/WorkerContext data structures, DAG engine, ConductorEngine, Tier 2/3/4 lifecycles, persona application.
- **[docs/guide_simulations.md](../docs/guide_simulations.md):** `live_gui` fixture, Puppeteer pattern, mock provider, test areas by subsystem. - **[docs/guide_simulations.md](../docs/guide_simulations.md):** `live_gui` fixture, Puppeteer pattern, mock provider, test areas by subsystem.
- **[docs/guide_testing.md](../docs/guide_testing.md):** **NEW** — 251 test files, 5 categories, 7 conftest fixtures (`isolate_workspace`, `reset_paths`, `reset_ai_client`, `vlogger`, `kill_process_tree`, `mock_app`, `live_gui` session-scoped), Puppeteer pattern, mock provider, structural testing contract. - **[docs/guide_testing.md](../docs/guide_testing.md):** **NEW** — 251 test files, 5 categories, 7 conftest fixtures (`isolate_workspace`, `reset_paths`, `reset_ai_client`, `vlogger`, `kill_process_tree`, `mock_app`, `live_gui` session-scoped), Puppeteer pattern, mock provider, structural testing contract.
- **[docs/guide_gui_2.md](../docs/guide_gui_2.md):** **NEW**`src/gui_2.py` (~437KB main GUI): App class lifecycle, ~90 module-level render functions, Multi-Viewport docks, panel registry, command palette integration, ImGuiScope context managers, hot reload support. - **[docs/guide_gui_2.md](../docs/guide_gui_2.md):** **NEW**`src/gui_2.py` (~437KB main GUI): App class lifecycle, ~90 module-level render functions, Multi-Viewport docks, panel registry, command palette integration, ImGuiScope context managers, hot reload support.
- **[docs/guide_ai_client.md](../docs/guide_ai_client.md):** **NEW**`src/ai_client.py` (~166KB): multi-provider LLM singleton (8 providers: gemini, anthropic, gemini_cli, deepseek, minimax, qwen, grok, llama), async dispatch via `asyncio.gather`, threading.local for source tier tagging, Anthropic ephemeral caching + Gemini explicit caching, Tier 4 QA error interception, inlined `VendorCapabilities` registry (moved from the deleted `src/vendor_capabilities.py`). - **[docs/guide_ai_client.md](../docs/guide_ai_client.md):** **NEW**`src/ai_client.py` (~166KB): multi-provider LLM singleton (8 providers: gemini, anthropic, deepseek, minimax, qwen, grok, llama), async dispatch via `asyncio.gather`, threading.local for source tier tagging, Anthropic ephemeral caching + Gemini explicit caching, Tier 4 QA error interception, inlined `VendorCapabilities` registry (moved from the deleted `src/vendor_capabilities.py`).
- **[docs/guide_api_hooks.md](../docs/guide_api_hooks.md):** **NEW**`src/api_hooks.py` + `src/api_hook_client.py` (~51KB + ~38KB): HookServer on `127.0.0.1:8999`, ApiHookClient wrapper, 8+ endpoints, Remote Confirmation Protocol via `/api/ask`. - **[docs/guide_api_hooks.md](../docs/guide_api_hooks.md):** **NEW**`src/api_hooks.py` + `src/api_hook_client.py` (~51KB + ~38KB): HookServer on `127.0.0.1:8999`, ApiHookClient wrapper, 8+ endpoints, Remote Confirmation Protocol via `/api/ask`.
- **[docs/guide_mcp_client.md](../docs/guide_mcp_client.md):** **NEW**`src/mcp_client.py` (~92KB, 45 tools; tool specs live in `src/mcp_tool_specs.py`): 3-layer security (Allowlist → Validate → Resolve), all native tools (File I/O, Python AST, C/C++ AST, Analysis, Network, Runtime, Beads), ExternalMCPManager (Stdio + SSE), JSON-RPC 2.0 engine. - **[docs/guide_mcp_client.md](../docs/guide_mcp_client.md):** **NEW**`src/mcp_client.py` (~92KB, 45 tools; tool specs live in `src/mcp_tool_specs.py`): 3-layer security (Allowlist → Validate → Resolve), all native tools (File I/O, Python AST, C/C++ AST, Analysis, Network, Runtime, Beads), ExternalMCPManager (Stdio + SSE), JSON-RPC 2.0 engine.
- **[docs/guide_app_controller.md](../docs/guide_app_controller.md):** **NEW**`src/app_controller.py` (~240KB): headless orchestrator, AppState dataclass, all subsystem managers, `_predefined_callbacks`/`_gettable_fields` Hook API registries, SyncEventQueue, headless mode. - **[docs/guide_app_controller.md](../docs/guide_app_controller.md):** **NEW**`src/app_controller.py` (~240KB): headless orchestrator, AppState dataclass, all subsystem managers, `_predefined_callbacks`/`_gettable_fields` Hook API registries, SyncEventQueue, headless mode.
- **[docs/guide_multi_agent_conductor.md](../docs/guide_multi_agent_conductor.md):** **NEW**`src/multi_agent_conductor.py` + `src/dag_engine.py` (~30KB + ~11KB): TrackDAG (iterative DFS cycle detection, Kahn's topological sort), ExecutionEngine (Auto-Queue / Step Mode), MultiAgentConductor + WorkerPool (concurrency 4), the WorkerPool's internal `run_worker_lifecycle` subprocess template (NOT the deprecated `mma_exec.py`; see `docs/guide_meta_boundary.md`). - **[docs/guide_multi_agent_conductor.md](../docs/guide_multi_agent_conductor.md):** **NEW**`src/multi_agent_conductor.py` + `src/dag_engine.py` (~30KB + ~11KB): TrackDAG (iterative DFS cycle detection, Kahn's topological sort), ExecutionEngine (Auto-Queue / Step Mode), MultiAgentConductor + WorkerPool (concurrency 4), the WorkerPool's internal `run_worker_lifecycle` subprocess template (NOT the deprecated `mma_exec.py`; see `docs/guide_meta_boundary.md`).
- **[docs/guide_models.md](../docs/guide_models.md):** **UPDATED 2026-07-02**`src/models.py` is now a ~1.5KB legacy re-export shim (`Metadata = TrackMetadata` alias + `PROVIDERS` lazy `__getattr__`). Data models moved to per-system files per `module_taxonomy_refactor_20260627`: `src/mma.py` (TrackMetadata, Ticket, Track, WorkerContext), `src/project_files.py` (FileItem), `src/type_aliases.py` (typed boundary + per-aggregate dataclasses), `src/mcp_tool_specs.py` (typed ToolSpec registry, 45 tools), `src/result_types.py` (Result[T], ErrorInfo). `VendorCapabilities` lives in `src/ai_client.py`. - **[docs/guide_models.md](../docs/guide_models.md):** **UPDATED 2026-07-02**`src/models.py` is now a ~1.5KB legacy re-export shim (`Metadata = TrackMetadata` alias + `PROVIDERS` lazy `__getattr__`). Data models moved to per-system files per `module_taxonomy_refactor_20260627`: `src/mma.py` (TrackMetadata, Ticket, Track, WorkerContext), `src/project_files.py` (FileItem), `src/type_aliases.py` (typed boundary + per-aggregate dataclasses), `src/mcp_tool_specs.py` (typed ToolSpec registry, 45 tools), `src/result_types.py` (Result[T], ErrorInfo). `VendorCapabilities` lives in `src/ai_client.py`.
- See [docs/Readme.md](../docs/Readme.md) for the full **41-guide index** covering context curation, shaders, RAG, Beads, hot reload, personas, NERV theme, workspace profiles, and command palette. - See [docs/Readme.md](../docs/Readme.md) for the full **41-guide index** covering context curation, shaders, RAG, Beads, hot reload, personas, NERV theme, workspace profiles, and command palette.
## Task Workflow ## Task Workflow
@@ -88,146 +88,146 @@ All tasks follow a strict lifecycle:
2. **Mark In Progress:** Before beginning work, edit `plan.md` and change the task from `[ ]` to `[~]` 2. **Mark In Progress:** Before beginning work, edit `plan.md` and change the task from `[ ]` to `[~]`
3. **High-Signal Research Phase:** 3. **High-Signal Research Phase:**
- **Identify Dependencies:** Use `list_directory`, `get_tree`, and `py_get_imports` to map file relations. - **Identify Dependencies:** Use `list_directory`, `get_tree`, and `py_get_imports` to map file relations.
- **Map Architecture:** Use `py_get_code_outline` or `py_get_skeleton` on identified files to understand their structure. - **Map Architecture:** Use `py_get_code_outline` or `py_get_skeleton` on identified files to understand their structure.
- **Audit State:** Use `py_get_code_outline` or `py_get_definition` on the target class's `__init__` method to check for existing, unused, or duplicate state variables before adding new ones. - **Audit State:** Use `py_get_code_outline` or `py_get_definition` on the target class's `__init__` method to check for existing, unused, or duplicate state variables before adding new ones.
- **Analyze Changes:** Use `get_git_diff` if the task involves modifying recently updated code. - **Analyze Changes:** Use `get_git_diff` if the task involves modifying recently updated code.
- **Minimize Token Burn:** Only use `read_file` with `start_line`/`end_line` for specific implementation details once target areas are identified. - **Minimize Token Burn:** Only use `read_file` with `start_line`/`end_line` for specific implementation details once target areas are identified.
4. **Write Failing Tests (Red Phase):** 4. **Write Failing Tests (Red Phase):**
- **Pre-Delegation Checkpoint:** Before spawning a worker for dangerous or non-trivial changes, ensure your current progress is staged (`git add .`) or committed. This prevents losing iterations if a sub-agent incorrectly uses `git restore`. - **Pre-Delegation Checkpoint:** Before spawning a worker for dangerous or non-trivial changes, ensure your current progress is staged (`git add .`) or committed. This prevents losing iterations if a sub-agent incorrectly uses `git restore`.
- **Zero-Assertion Ban:** You MUST NOT write tests that contain only `pass` or lack meaningful assertions. A test is only valid if it contains assertions that explicitly test the behavioral change and verify the failure condition. - **Zero-Assertion Ban:** You MUST NOT write tests that contain only `pass` or lack meaningful assertions. A test is only valid if it contains assertions that explicitly test the behavioral change and verify the failure condition.
- **Code Style:** ALWAYS explicitly mention "Use exactly 1-space indentation for Python code" when prompting a sub-agent. - **Code Style:** ALWAYS explicitly mention "Use exactly 1-space indentation for Python code" when prompting a sub-agent.
- **Delegate Test Creation:** Do NOT write test code directly. Spawn a Tier 3 Worker via the **OpenCode Task tool** with `subagent_type: "tier3-worker"` and a **surgical prompt** specifying WHERE (file:line range), WHAT (test to create), HOW (which assertions/fixtures to use), and SAFETY (thread constraints if applicable). Example: `"Write tests in tests/test_cost_tracker.py for cost_tracker.py:estimate_cost(). Test all model patterns in MODEL_PRICING dict. Assert unknown model returns 0. Use 1-space indentation."` (If repeating due to failures, set the subagent's `failure_count` higher to switch to a more capable model.) **Note:** the legacy `python scripts/mma_exec.py --role tier3-worker` invocation is DEPRECATED (see §"Conductor Token Firewalling" below); use the OpenCode Task tool instead. - **Delegate Test Creation:** Do NOT write test code directly. Spawn a Tier 3 Worker via the **OpenCode Task tool** with `subagent_type: "tier3-worker"` and a **surgical prompt** specifying WHERE (file:line range), WHAT (test to create), HOW (which assertions/fixtures to use), and SAFETY (thread constraints if applicable). Example: `"Write tests in tests/test_cost_tracker.py for cost_tracker.py:estimate_cost(). Test all model patterns in MODEL_PRICING dict. Assert unknown model returns 0. Use 1-space indentation."` (If repeating due to failures, set the subagent's `failure_count` higher to switch to a more capable model.) **Note:** the legacy `python scripts/mma_exec.py --role tier3-worker` invocation is DEPRECATED (see §"Conductor Token Firewalling" below); use the OpenCode Task tool instead.
- Take the code generated by the Worker and apply it. - Take the code generated by the Worker and apply it.
- **CRITICAL:** Run the tests and confirm that they fail as expected. This is the "Red" phase of TDD. Do not proceed until you have failing tests. - **CRITICAL:** Run the tests and confirm that they fail as expected. This is the "Red" phase of TDD. Do not proceed until you have failing tests.
5. **Implement to Pass Tests (Green Phase):** 5. **Implement to Pass Tests (Green Phase):**
- **Pre-Delegation Checkpoint:** Ensure current progress is staged or committed before delegating. - **Pre-Delegation Checkpoint:** Ensure current progress is staged or committed before delegating.
- **Code Style:** ALWAYS explicitly mention "Use exactly 1-space indentation for Python code" when prompting a sub-agent. - **Code Style:** ALWAYS explicitly mention "Use exactly 1-space indentation for Python code" when prompting a sub-agent.
- **Delegate Implementation:** Do NOT write the implementation code directly. Spawn a Tier 3 Worker via the **OpenCode Task tool** (`subagent_type: "tier3-worker"`) with a **surgical prompt** specifying WHERE (file:line range to modify), WHAT (the specific change), HOW (which API calls, data structures, or patterns to use), and SAFETY (thread-safety constraints). Example: `"In gui_2.py _render_mma_dashboard (lines 2685-2699), extend the token usage table from 3 to 5 columns. Add 'Model' and 'Est. Cost' using imgui.table_setup_column(). Call cost_tracker.estimate_cost(model, input_tokens, output_tokens). Use 1-space indentation."` (If repeating due to failures, set `failure_count` higher to switch to a more capable model.) **Note:** the legacy `python scripts/mma_exec.py --role tier3-worker` invocation is DEPRECATED; use the OpenCode Task tool. - **Delegate Implementation:** Do NOT write the implementation code directly. Spawn a Tier 3 Worker via the **OpenCode Task tool** (`subagent_type: "tier3-worker"`) with a **surgical prompt** specifying WHERE (file:line range to modify), WHAT (the specific change), HOW (which API calls, data structures, or patterns to use), and SAFETY (thread-safety constraints). Example: `"In gui_2.py _render_mma_dashboard (lines 2685-2699), extend the token usage table from 3 to 5 columns. Add 'Model' and 'Est. Cost' using imgui.table_setup_column(). Call cost_tracker.estimate_cost(model, input_tokens, output_tokens). Use 1-space indentation."` (If repeating due to failures, set `failure_count` higher to switch to a more capable model.) **Note:** the legacy `python scripts/mma_exec.py --role tier3-worker` invocation is DEPRECATED; use the OpenCode Task tool.
- Take the code generated by the Worker and apply it. - Take the code generated by the Worker and apply it.
- Run the test suite again and confirm that all tests now pass. This is the "Green" phase. - Run the test suite again and confirm that all tests now pass. This is the "Green" phase.
6. **Refactor (Optional but Recommended):** 6. **Refactor (Optional but Recommended):**
- With the safety of passing tests, refactor the implementation code and the test code to improve clarity, remove duplication, and enhance performance without changing the external behavior. - With the safety of passing tests, refactor the implementation code and the test code to improve clarity, remove duplication, and enhance performance without changing the external behavior.
- Rerun tests to ensure they still pass after refactoring. - Rerun tests to ensure they still pass after refactoring.
7. **Verify Coverage:** Run coverage reports using the project's chosen tools. For example, in a Python project, this might look like: 7. **Verify Coverage:** Run coverage reports using the project's chosen tools. For example, in a Python project, this might look like:
```powershell ```powershell
pytest --cov=app --cov-report=html pytest --cov=app --cov-report=html
``` ```
Target: >80% coverage for new code. The specific tools and commands will vary by language and framework. Target: >80% coverage for new code. The specific tools and commands will vary by language and framework.
8. **Document Deviations:** If implementation differs from tech stack: 8. **Document Deviations:** If implementation differs from tech stack:
- **STOP** implementation - **STOP** implementation
- Update `tech-stack.md` with new design - Update `tech-stack.md` with new design
- Add dated note explaining the change - Add dated note explaining the change
- Resume implementation - Resume implementation
9. **Commit Code Changes:** 9. **Commit Code Changes:**
- **CRITICAL - ATOMIC PER-TASK COMMITS**: You MUST commit your changes immediately after completing and verifying a single task. Do NOT move on to the next task in the plan without committing the current one. This ensures precise tracking and safe rollback points. - **CRITICAL - ATOMIC PER-TASK COMMITS**: You MUST commit your changes immediately after completing and verifying a single task. Do NOT move on to the next task in the plan without committing the current one. This ensures precise tracking and safe rollback points.
- Stage all code changes related to the task. - Stage all code changes related to the task.
- Propose a clear, concise commit message e.g, `feat(ui): Create basic HTML structure for calculator`. - Propose a clear, concise commit message e.g, `feat(ui): Create basic HTML structure for calculator`.
- Perform the commit. - Perform the commit.
10. **Attach Task Summary with Git Notes:** 10. **Attach Task Summary with Git Notes:**
- **Step 9.1: Get Commit Hash:** Obtain the hash of the *just-completed commit* (`git log -1 --format="%H"`). - **Step 9.1: Get Commit Hash:** Obtain the hash of the *just-completed commit* (`git log -1 --format="%H"`).
- **Step 9.2: Draft Note Content:** Create a detailed summary for the completed task. This should include the task name, a summary of changes, a list of all created/modified files, and the core "why" for the change. - **Step 9.2: Draft Note Content:** Create a detailed summary for the completed task. This should include the task name, a summary of changes, a list of all created/modified files, and the core "why" for the change.
- **Step 9.3: Attach Note:** Use the `git notes` command to attach the summary to the commit. - **Step 9.3: Attach Note:** Use the `git notes` command to attach the summary to the commit.
```powershell ```powershell
# The note content from the previous step is passed via the -m flag. # The note content from the previous step is passed via the -m flag.
git notes add -m "<note content>" <commit_hash> git notes add -m "<note content>" <commit_hash>
``` ```
11. **Get and Record Task Commit SHA:** 11. **Get and Record Task Commit SHA:**
- **Step 10.1: Update Plan:** Read `plan.md`, find the line for the completed task, update its status from `[~]` to `[x]`, and append the first 7 characters of the *just-completed commit's* commit hash. - **Step 10.1: Update Plan:** Read `plan.md`, find the line for the completed task, update its status from `[~]` to `[x]`, and append the first 7 characters of the *just-completed commit's* commit hash.
- **Step 10.2: Write Plan:** Write the updated content back to `plan.md`. - **Step 10.2: Write Plan:** Write the updated content back to `plan.md`.
12. **Commit Plan Update:** 12. **Commit Plan Update:**
- **Action:** Stage the modified `plan.md` file. - **Action:** Stage the modified `plan.md` file.
- **Action:** Commit this change with a descriptive message (e.g., `conductor(plan): Mark task 'Create user model' as complete`). - **Action:** Commit this change with a descriptive message (e.g., `conductor(plan): Mark task 'Create user model' as complete`).
### Phase Completion Verification and Checkpointing Protocol ### Phase Completion Verification and Checkpointing Protocol
**Trigger:** This protocol is executed immediately after a task is completed that also concludes a phase in `plan.md`. **Trigger:** This protocol is executed immediately after a task is completed that also concludes a phase in `plan.md`.
1. **Announce Protocol Start:** Inform the user that the phase is complete and the verification and checkpointing protocol has begun. 1. **Announce Protocol Start:** Inform the user that the phase is complete and the verification and checkpointing protocol has begun.
2. **Ensure Test Coverage for Phase Changes:** 2. **Ensure Test Coverage for Phase Changes:**
- **Step 2.1: Determine Phase Scope:** To identify the files changed in this phase, you must first find the starting point. Read `plan.md` to find the Git commit SHA of the *previous* phase's checkpoint. If no previous checkpoint exists, the scope is all changes since the first commit. - **Step 2.1: Determine Phase Scope:** To identify the files changed in this phase, you must first find the starting point. Read `plan.md` to find the Git commit SHA of the *previous* phase's checkpoint. If no previous checkpoint exists, the scope is all changes since the first commit.
- **Step 2.2: List Changed Files:** Execute `git diff --name-only <previous_checkpoint_sha> HEAD` to get a precise list of all files modified during this phase. - **Step 2.2: List Changed Files:** Execute `git diff --name-only <previous_checkpoint_sha> HEAD` to get a precise list of all files modified during this phase.
- **Step 2.3: Verify and Create Tests:** For each file in the list: - **Step 2.3: Verify and Create Tests:** For each file in the list:
- **CRITICAL:** First, check its extension. Exclude non-code files (e.g., `.json`, `.md`, `.yaml`). - **CRITICAL:** First, check its extension. Exclude non-code files (e.g., `.json`, `.md`, `.yaml`).
- For each remaining code file, verify a corresponding test file exists. - For each remaining code file, verify a corresponding test file exists.
- If a test file is missing, you **must** create one. Before writing the test, **first, analyze other test files in the repository to determine the correct naming convention and testing style.** The new tests **must** validate the functionality described in this phase's tasks (`plan.md`). - If a test file is missing, you **must** create one. Before writing the test, **first, analyze other test files in the repository to determine the correct naming convention and testing style.** The new tests **must** validate the functionality described in this phase's tasks (`plan.md`).
3. **Execute Automated Tests in Batches:** 3. **Execute Automated Tests in Batches:**
- Because the full suite is large (>360 tests) and contains complex UI simulations, running the entire suite frequently can lead to random timeouts or threading access violations. - Because the full suite is large (>360 tests) and contains complex UI simulations, running the entire suite frequently can lead to random timeouts or threading access violations.
- Before execution, you **must** announce the exact shell command. - Before execution, you **must** announce the exact shell command.
- **CRITICAL:** When verifying changes, **do not run the full suite (`pytest tests/`)**. Instead, run tests in small, targeted batches (maximum 4 test files at a time). Only use long timeouts (`--timeout=60` or `--timeout=120`) if the specific tests in the batch are known to be slow (e.g., simulation tests). - **CRITICAL:** When verifying changes, **do not run the full suite (`pytest tests/`)**. Instead, run tests in small, targeted batches (maximum 4 test files at a time). Only use long timeouts (`--timeout=60` or `--timeout=120`) if the specific tests in the batch are known to be slow (e.g., simulation tests).
- **Example Announcement:** "I will now run the automated test suite to verify the phase. **Command:** `uv run pytest tests/test_specific_feature.py`" - **Example Announcement:** "I will now run the automated test suite to verify the phase. **Command:** `uv run pytest tests/test_specific_feature.py`"
- Execute the announced command. - Execute the announced command.
- If tests fail with significant output (e.g., a large traceback), **DO NOT** attempt to read the raw `stderr` directly into your context. Instead, pipe the output to a log file and **spawn a Tier 4 QA Agent via the OpenCode Task tool (`subagent_type: "tier4-qa"`)** with the error output + an explicit instruction "DO NOT fix — provide root cause analysis only". (The legacy `python scripts/mma_exec.py --role tier4-qa` invocation is DEPRECATED; use the OpenCode Task tool.) - If tests fail with significant output (e.g., a large traceback), **DO NOT** attempt to read the raw `stderr` directly into your context. Instead, pipe the output to a log file and **spawn a Tier 4 QA Agent via the OpenCode Task tool (`subagent_type: "tier4-qa"`)** with the error output + an explicit instruction "DO NOT fix — provide root cause analysis only". (The legacy `python scripts/mma_exec.py --role tier4-qa` invocation is DEPRECATED; use the OpenCode Task tool.)
- You **must** inform the user and begin debugging using the QA Agent's summary. You may attempt to propose a fix a **maximum of two times**. If the tests still fail after your second proposed fix, you **must stop**, report the persistent failure, and ask the user for guidance. - You **must** inform the user and begin debugging using the QA Agent's summary. You may attempt to propose a fix a **maximum of two times**. If the tests still fail after your second proposed fix, you **must stop**, report the persistent failure, and ask the user for guidance.
4. **Execute Automated API Hook Verification:** 4. **Execute Automated API Hook Verification:**
- **CRITICAL:** The Conductor agent will now automatically execute verification tasks using the application's API hooks. - **CRITICAL:** The Conductor agent will now automatically execute verification tasks using the application's API hooks.
- The agent will announce the start of the automated verification to the user. - The agent will announce the start of the automated verification to the user.
- It will then communicate with the application's IPC server to trigger the necessary verification functions. - It will then communicate with the application's IPC server to trigger the necessary verification functions.
- **Result Handling:** - **Result Handling:**
- All results (successes and failures) from the API hook invocations will be logged. - All results (successes and failures) from the API hook invocations will be logged.
- If all automated verifications pass, the agent will inform the user and proceed to the next step (Create Checkpoint Commit). - If all automated verifications pass, the agent will inform the user and proceed to the next step (Create Checkpoint Commit).
- If any automated verification fails, the agent will halt the workflow, present the detailed failure logs to the user, and await further instructions for debugging or remediation. - If any automated verification fails, the agent will halt the workflow, present the detailed failure logs to the user, and await further instructions for debugging or remediation.
5. **Present Automated Verification Results and User Confirmation:** 5. **Present Automated Verification Results and User Confirmation:**
- After executing automated verification, the Conductor agent will present the results to the user. - After executing automated verification, the Conductor agent will present the results to the user.
- If verification passed, the agent will state: "Automated verification completed successfully." - If verification passed, the agent will state: "Automated verification completed successfully."
- If verification failed, the agent will state: "Automated verification failed. Please review the logs above for details. You may attempt to propose a fix a **maximum of two times**. If the tests still fail after your second proposed fix, you **must stop**, report the persistent failure, and ask the user for guidance." - If verification failed, the agent will state: "Automated verification failed. Please review the logs above for details. You may attempt to propose a fix a **maximum of two times**. If the tests still fail after your second proposed fix, you **must stop**, report the persistent failure, and ask the user for guidance."
- **PAUSE** and await the user's response. Do not proceed without an explicit yes or confirmation from the user to proceed if tests pass, or guidance if tests fail. - **PAUSE** and await the user's response. Do not proceed without an explicit yes or confirmation from the user to proceed if tests pass, or guidance if tests fail.
6. **Create Checkpoint Commit:** 6. **Create Checkpoint Commit:**
- Stage all changes. If no changes occurred in this step, proceed with an empty commit. - Stage all changes. If no changes occurred in this step, proceed with an empty commit.
- Perform the commit with a clear and concise message (e.g., `conductor(checkpoint): Checkpoint end of Phase X`). - Perform the commit with a clear and concise message (e.g., `conductor(checkpoint): Checkpoint end of Phase X`).
7. **Attach Auditable Verification Report using Git Notes:** 7. **Attach Auditable Verification Report using Git Notes:**
- **Step 7.1: Draft Note Content:** Create a detailed verification report including the automated test command, the manual verification steps, and the user's confirmation. - **Step 7.1: Draft Note Content:** Create a detailed verification report including the automated test command, the manual verification steps, and the user's confirmation.
- **Step 7.2: Attach Note:** Use the `git notes` command and the full commit hash from the previous step to attach the full report to the checkpoint commit. - **Step 7.2: Attach Note:** Use the `git notes` command and the full commit hash from the previous step to attach the full report to the checkpoint commit.
8. **Get and Record Phase Checkpoint SHA:** 8. **Get and Record Phase Checkpoint SHA:**
- **Step 8.1: Get Commit Hash:** Obtain the hash of the *just-created checkpoint commit* (`git log -1 --format="%H"`). - **Step 8.1: Get Commit Hash:** Obtain the hash of the *just-created checkpoint commit* (`git log -1 --format="%H"`).
- **Step 8.2: Update Plan:** Read `plan.md`, find the heading for the completed phase, and append the first 7 characters of the commit hash in the format `[checkpoint: <sha>]`. - **Step 8.2: Update Plan:** Read `plan.md`, find the heading for the completed phase, and append the first 7 characters of the commit hash in the format `[checkpoint: <sha>]`.
- **Step 8.3: Write Plan:** Write the updated content back to `plan.md`. - **Step 8.3: Write Plan:** Write the updated content back to `plan.md`.
9. **Commit Plan Update:** 9. **Commit Plan Update:**
- **Action:** Stage the modified `plan.md` file. - **Action:** Stage the modified `plan.md` file.
- **Action:** Commit this change with a descriptive message following the format `conductor(plan): Mark phase '<PHASE NAME>' as complete`. - **Action:** Commit this change with a descriptive message following the format `conductor(plan): Mark phase '<PHASE NAME>' as complete`.
10. **Announce Completion:** Inform the user that the phase is complete and the checkpoint has been created, with the detailed verification report attached as a git note. 10. **Announce Completion:** Inform the user that the phase is complete and the checkpoint has been created, with the detailed verification report attached as a git note.
### Verification via API Hooks ### Verification via API Hooks
For features involving the GUI or complex internal state, unit tests are often insufficient. You MUST use the application's built-in API hooks for empirical verification: For features involving the GUI or complex internal state, unit tests are often insufficient. You MUST use the application's built-in API hooks for empirical verification:
1. **Launch the App with Hooks:** Run the application in a separate shell with the `--enable-test-hooks` flag: 1. **Launch the App with Hooks:** Run the application in a separate shell with the `--enable-test-hooks` flag:
```powershell ```powershell
uv run python gui.py --enable-test-hooks uv run python gui.py --enable-test-hooks
``` ```
This starts the hook server on port `8999`. This starts the hook server on port `8999`.
2. **Use the pytest `live_gui` Fixture:** For automated tests, use the session-scoped `live_gui` fixture defined in `tests/conftest.py`. This fixture handles the lifecycle (startup/shutdown) of the application with hooks enabled. 2. **Use the pytest `live_gui` Fixture:** For automated tests, use the session-scoped `live_gui` fixture defined in `tests/conftest.py`. This fixture handles the lifecycle (startup/shutdown) of the application with hooks enabled.
```python ```python
def test_my_feature(live_gui): def test_my_feature(live_gui):
# The GUI is now running on port 8999 # The GUI is now running on port 8999
... ...
``` ```
Note: pytest must be run with `uv`. Note: pytest must be run with `uv`.
3. **Verify via ApiHookClient:** Use the `ApiHookClient` in `api_hook_client.py` to interact with the running application. It includes robust retry logic and health checks. 3. **Verify via ApiHookClient:** Use the `ApiHookClient` in `api_hook_client.py` to interact with the running application. It includes robust retry logic and health checks.
4. **Verify via REST Commands:** Use PowerShell or `curl` to send commands to the application and verify the response. For example, to check health: 4. **Verify via REST Commands:** Use PowerShell or `curl` to send commands to the application and verify the response. For example, to check health:
```powershell ```powershell
Invoke-RestMethod -Uri "http://127.0.0.1:8999/status" -Method Get Invoke-RestMethod -Uri "http://127.0.0.1:8999/status" -Method Get
``` ```
### Quality Gates ### Quality Gates
@@ -275,9 +275,9 @@ Before marking any task complete, verify:
### Structural Testing Contract ### Structural Testing Contract
1. **Ban on Arbitrary Core Mocking:** Tier 3 workers are strictly forbidden from using `unittest.mock.patch` to bypass or stub core infrastructure (e.g., event queues, `ai_client` internals, threading primitives) unless explicitly authorized by the Tier 2 Tech Lead for a specific boundary test. 1. **Ban on Arbitrary Core Mocking:** Tier 3 workers are strictly forbidden from using `unittest.mock.patch` to bypass or stub core infrastructure (e.g., event queues, `ai_client` internals, threading primitives) unless explicitly authorized by the Tier 2 Tech Lead for a specific boundary test.
2. **`live_gui` Standard:** All integration and end-to-end testing must utilize the `live_gui` fixture to interact with a real instance of the application via the Hook API. Bypassing the hook server to directly mutate GUI state in tests is prohibited. 2. **`live_gui` Standard:** All integration and end-to-end testing must utilize the `live_gui` fixture to interact with a real instance of the application via the Hook API. Bypassing the hook server to directly mutate GUI state in tests is prohibited.
3. **Artifact Isolation:** All test-generated artifacts (logs, temporary workspaces, mock outputs) MUST be written to the `tests/artifacts/` or `tests/logs/` directories. These directories are git-ignored to prevent repository pollution. 3. **Artifact Isolation:** All test-generated artifacts (logs, temporary workspaces, mock outputs) MUST be written to the `tests/artifacts/` or `tests/logs/` directories. These directories are git-ignored to prevent repository pollution.
### Unit Testing ### Unit Testing
@@ -368,14 +368,14 @@ This doc describes **META-TOOLING** — the AI agent orchestration layer used by
- **Mandatory Skill Activation:** As the very first step of any MMA-driven process, including track initialization and implementation phases, the agent MUST activate the `mma-orchestrator` skill (`activate_skill mma-orchestrator`) and their corresponding role's specific tier skill. This is crucial for enforcing the 4-Tier token firewall. - **Mandatory Skill Activation:** As the very first step of any MMA-driven process, including track initialization and implementation phases, the agent MUST activate the `mma-orchestrator` skill (`activate_skill mma-orchestrator`) and their corresponding role's specific tier skill. This is crucial for enforcing the 4-Tier token firewall.
- **The Sub-Agent Bridge (OpenCode Task tool):** All meta-tooling tiered delegation is now via the OpenCode Task tool with the appropriate `subagent_type`. This is the canonical META-TOOLING mechanism; it replaces the legacy `mma_exec.py` invocation. (The application-domain MMA engine in `src/multi_agent_conductor.py` is unchanged and is documented in `docs/guide_multi_agent_conductor.md`.) - **The Sub-Agent Bridge (OpenCode Task tool):** All meta-tooling tiered delegation is now via the OpenCode Task tool with the appropriate `subagent_type`. This is the canonical META-TOOLING mechanism; it replaces the legacy `mma_exec.py` invocation. (The application-domain MMA engine in `src/multi_agent_conductor.py` is unchanged and is documented in `docs/guide_multi_agent_conductor.md`.)
- **Model Tiers:** - **Model Tiers:**
- **Tier 1 (Strategic/Orchestration):** `gemini-3.1-pro-preview`. Focused on product alignment, setup (`/conductor:setup`), and track initialization (`/conductor:newTrack`). - **Tier 1 (Strategic/Orchestration):** `gemini-3.1-pro-preview`. Focused on product alignment, setup (`/conductor:setup`), and track initialization (`/conductor:newTrack`).
- **Tier 2 (Architectural/Tech Lead):** `gemini-3-flash-preview`. Focused on architectural design and track execution (`/conductor:implement`). **Note:** Tier 2 maintains persistent memory throughout a track's implementation. - **Tier 2 (Architectural/Tech Lead):** `gemini-3-flash-preview`. Focused on architectural design and track execution (`/conductor:implement`). **Note:** Tier 2 maintains persistent memory throughout a track's implementation.
- **Tier 3 (Execution/Worker):** `gemini-2.5-flash-lite`. Used for surgical code implementation and test generation. Operates statelessly (Context Amnesia) but has access to file I/O tools. - **Tier 3 (Execution/Worker):** `gemini-2.5-flash-lite`. Used for surgical code implementation and test generation. Operates statelessly (Context Amnesia) but has access to file I/O tools.
- **Tier 4 (Utility/QA):** `gemini-2.5-flash-lite`. Used for log summarization and error analysis. Operates statelessly (Context Amnesia) but has access to diagnostic tools. - **Tier 4 (Utility/QA):** `gemini-2.5-flash-lite`. Used for log summarization and error analysis. Operates statelessly (Context Amnesia) but has access to diagnostic tools.
- **Tiered Delegation Protocol (OpenCode Task tool):** - **Tiered Delegation Protocol (OpenCode Task tool):**
- **Tier 3 Worker:** invoke the Task tool with `subagent_type: "tier3-worker"`, providing a surgical prompt with WHERE/WHAT/HOW/SAFETY/COMMIT structure. **DO NOT** use `python scripts/mma_exec.py --role tier3-worker` (deprecated). - **Tier 3 Worker:** invoke the Task tool with `subagent_type: "tier3-worker"`, providing a surgical prompt with WHERE/WHAT/HOW/SAFETY/COMMIT structure. **DO NOT** use `python scripts/mma_exec.py --role tier3-worker` (deprecated).
- **Tier 4 QA Agent:** invoke the Task tool with `subagent_type: "tier4-qa"`, providing the error output + an explicit instruction "DO NOT fix — provide root cause analysis only". - **Tier 4 QA Agent:** invoke the Task tool with `subagent_type: "tier4-qa"`, providing the error output + an explicit instruction "DO NOT fix — provide root cause analysis only".
- **Tier 1 Orchestrator:** invoke the Task tool with `subagent_type: "tier1-orchestrator"` for track planning tasks. - **Tier 1 Orchestrator:** invoke the Task tool with `subagent_type: "tier1-orchestrator"` for track planning tasks.
- **MMA Skill Discipline Tests:** The 5 MMA skills (`mma-orchestrator`, `mma-tier1-orchestrator`, `mma-tier2-tech-lead`, `mma-tier3-worker`, `mma-tier4-qa`) at `.agents/skills/mma-*/SKILL.md` are tested for discipline compliance via `tests/test_mma_skill_discipline.py` (per `conductor/tracks/superpowers_review_apply_high_20260705/spec.md` §3.2 and recommendation #2 from `conductor/tracks/superpowers_review_20260619/decisions.md`). The tests are static-analysis of skill documents (text-pattern assertions), not behavioral tests; they run in <5 seconds, require no live_gui or MMA execution, and verify that load-bearing rules are *prominently documented* (not just buried in prose). Future agents extending the MMA skills must also extend the tests. - **MMA Skill Discipline Tests:** The 5 MMA skills (`mma-orchestrator`, `mma-tier1-orchestrator`, `mma-tier2-tech-lead`, `mma-tier3-worker`, `mma-tier4-qa`) at `.agents/skills/mma-*/SKILL.md` are tested for discipline compliance via `tests/test_mma_skill_discipline.py` (per `conductor/tracks/superpowers_review_apply_high_20260705/spec.md` §3.2 and recommendation #2 from `conductor/tracks/superpowers_review_20260619/decisions.md`). The tests are static-analysis of skill documents (text-pattern assertions), not behavioral tests; they run in <5 seconds, require no live_gui or MMA execution, and verify that load-bearing rules are *prominently documented* (not just buried in prose). Future agents extending the MMA skills must also extend the tests.
- **Observability:** All hierarchical interactions are recorded in `logs/mma_delegation.log` and detailed sub-agent logs are saved to `logs/agents/`. (These logs are populated by the OpenCode Task tool's logging layer.) - **Observability:** All hierarchical interactions are recorded in `logs/mma_delegation.log` and detailed sub-agent logs are saved to `logs/agents/`. (These logs are populated by the OpenCode Task tool's logging layer.)
@@ -470,14 +470,14 @@ The pattern `push_event(...)` → `time.sleep(N)` → `assert` is a guaranteed r
```python ```python
# WRONG: race condition # WRONG: race condition
def test_open_modal(live_gui): def test_open_modal(live_gui):
client.push_event("custom_callback", {"callback": "_toggle_settings", "args": []}) client.push_event("custom_callback", {"callback": "_toggle_settings", "args": []})
time.sleep(1) # hope the modal opened time.sleep(1) # hope the modal opened
assert some_cached_value["settings_open"] is True # may be stale assert some_cached_value["settings_open"] is True # may be stale
# RIGHT: poll-until-state-visible # RIGHT: poll-until-state-visible
def test_open_modal(live_gui): def test_open_modal(live_gui):
client.push_event("custom_callback", {"callback": "_toggle_settings", "args": []}) client.push_event("custom_callback", {"callback": "_toggle_settings", "args": []})
assert client.get_value("show_settings_modal"), "settings modal did not open" assert client.get_value("show_settings_modal"), "settings modal did not open"
``` ```
This pattern surfaced 5+ times in the 2026-06-10 batch-green wave (test_reset_session_clears_mma_and_rag, test_visual_mma, test_visual_sim_gui_ux, test_gui_ux_event_routing, test_z_negative_flows). The fix is always the same: replace `time.sleep` with a poll loop bounded by a retry timeout (typically 5-20 iterations × 0.5s). This pattern surfaced 5+ times in the 2026-06-10 batch-green wave (test_reset_session_clears_mma_and_rag, test_visual_mma, test_visual_sim_gui_ux, test_gui_ux_event_routing, test_z_negative_flows). The fix is always the same: replace `time.sleep` with a poll loop bounded by a retry timeout (typically 5-20 iterations × 0.5s).
@@ -497,9 +497,9 @@ This pattern surfaced 5+ times in the 2026-06-10 batch-green wave (test_reset_se
**How to detect during TDD:** **How to detect during TDD:**
- After modifying a class body, walk the AST and verify all expected methods are class-level: - After modifying a class body, walk the AST and verify all expected methods are class-level:
```bash ```bash
uv run python -c "import ast; tree = ast.parse(open('src/gui_2.py').read()); [print(item.name) for n in ast.walk(tree) if isinstance(n, ast.ClassDef) and n.name == 'App' for item in n.body if isinstance(item, ast.FunctionDef)]" uv run python -c "import ast; tree = ast.parse(open('src/gui_2.py').read()); [print(item.name) for n in ast.walk(tree) if isinstance(n, ast.ClassDef) and n.name == 'App' for item in n.body if isinstance(item, ast.FunctionDef)]"
``` ```
- The skeleton via `manual-slop_py_get_skeleton` should show the method as a class member. If it's missing, it's nested. - The skeleton via `manual-slop_py_get_skeleton` should show the method as a class member. If it's missing, it's nested.
**How to fix:** Re-indent the affected method to exactly 2-space class level. Use the file_slice tool or PyCharm-style auto-format to verify. Run the failing test to confirm. **How to fix:** Re-indent the affected method to exactly 2-space class level. Use the file_slice tool or PyCharm-style auto-format to verify. Run the failing test to confirm.
@@ -695,42 +695,42 @@ Audit, Goals, Non-Goals, Architecture, Risks, Verification, etc.) with
these specific Tier 1 rules: these specific Tier 1 rules:
- **Current State Audit is MANDATORY** before writing requirements. Read - **Current State Audit is MANDATORY** before writing requirements. Read
the actual code with MCP tools (`get_file_slice`, `py_get_skeleton`, the actual code with MCP tools (`get_file_slice`, `py_get_skeleton`,
`py_get_definition`, `py_find_usages`). Document existing `py_get_definition`, `py_find_usages`). Document existing
implementations with `file:line` references in a "Current State implementations with `file:line` references in a "Current State
Audit" section. Failure to audit = track failure. Audit" section. Failure to audit = track failure.
- **Frame requirements as GAPS, not features.** "The existing X - **Frame requirements as GAPS, not features.** "The existing X
(file.py:L100-200) has Y; this track fills the gap" — not "Build (file.py:L100-200) has Y; this track fills the gap" — not "Build
feature Z". feature Z".
- **Write worker-ready tasks** in the plan. Each plan task must be - **Write worker-ready tasks** in the plan. Each plan task must be
executable by a Tier 3 worker. The Tier 1 does NOT execute the executable by a Tier 3 worker. The Tier 1 does NOT execute the
plan; the Tier 1 writes it for a Tier 3 to execute. plan; the Tier 1 writes it for a Tier 3 to execute.
- **Reference architecture docs** (`docs/guide_*.md`, - **Reference architecture docs** (`docs/guide_*.md`,
`conductor/code_styleguides/*.md`) in every spec. Every requirement `conductor/code_styleguides/*.md`) in every spec. Every requirement
must point to the existing pattern it follows (or the new pattern it must point to the existing pattern it follows (or the new pattern it
establishes). establishes).
- **For bug fix tracks: Root Cause Analysis** is mandatory. Read the - **For bug fix tracks: Root Cause Analysis** is mandatory. Read the
code, trace the data flow, list specific root cause candidates. code, trace the data flow, list specific root cause candidates.
Don't ship "I tried X, the test still failed, here's a 200-line Don't ship "I tried X, the test still failed, here's a 200-line
report". report".
### 3. Metadata format ### 3. Metadata format
The `metadata.json` follows the standard schema. Specific Tier 1 rules: The `metadata.json` follows the standard schema. Specific Tier 1 rules:
- `scope.new_files` / `scope.modified_files` / `scope.deleted_files` - `scope.new_files` / `scope.modified_files` / `scope.deleted_files`
are the file-level scope. No "lines of code changed" estimates. are the file-level scope. No "lines of code changed" estimates.
- `regressions_and_pre_existing_failures` is a list, not a count. - `regressions_and_pre_existing_failures` is a list, not a count.
- `pre_existing_failures_remaining` MUST be `[]` for the track to be - `pre_existing_failures_remaining` MUST be `[]` for the track to be
marked complete. marked complete.
- `deferred_to_followup_tracks` is a list of followup items with - `deferred_to_followup_tracks` is a list of followup items with
title + description + track_status. No "estimated effort". title + description + track_status. No "estimated effort".
- `estimated_effort` field uses `method: "scope (per workflow.md §Tier - `estimated_effort` field uses `method: "scope (per workflow.md §Tier
1 Track Initialization Rules). NO day estimates."` and a per-phase 1 Track Initialization Rules). NO day estimates."` and a per-phase
`scope` summary (e.g., `phase_1: "1 task: investigation"`). `scope` summary (e.g., `phase_1: "1 task: investigation"`).
- `risk_register` entries use scope-relative likelihood ("medium" - `risk_register` entries use scope-relative likelihood ("medium"
means "the implementation may be larger than the spec suggests"), means "the implementation may be larger than the spec suggests"),
not time-relative ("takes longer than 2 days"). not time-relative ("takes longer than 2 days").
### 4. Plan format ### 4. Plan format
@@ -738,11 +738,11 @@ The `plan.md` follows the standard TDD red-first template. Specific
Tier 1 rules: Tier 1 rules:
- Each task has WHERE / WHAT / HOW / SAFETY / COMMIT / GIT NOTE - Each task has WHERE / WHAT / HOW / SAFETY / COMMIT / GIT NOTE
fields. Tasks are NOT grouped by "day" or "hour". fields. Tasks are NOT grouped by "day" or "hour".
- Phase headers describe the WORK, not the TIME. ("Phase 1: - Phase headers describe the WORK, not the TIME. ("Phase 1:
Investigation" not "Phase 1: Day 1"). Investigation" not "Phase 1: Day 1").
- The plan is read by a Tier 3 worker; the Tier 1 never executes it - The plan is read by a Tier 3 worker; the Tier 1 never executes it
themselves. themselves.
### 5. The "Reasonable effort" guard ### 5. The "Reasonable effort" guard
@@ -770,8 +770,8 @@ Every track's `conductor/tracks/<track_id>/state.toml` should follow this struct
[meta] [meta]
track_id = "<track_id>" track_id = "<track_id>"
name = "<Human-Readable Name>" name = "<Human-Readable Name>"
status = "active" # active | completed status = "active" # active | completed
current_phase = 0 # 0 = pre-Phase 1; 1..N = in Phase N; "complete" if all phases done current_phase = 0 # 0 = pre-Phase 1; 1..N = in Phase N; "complete" if all phases done
last_updated = "<YYYY-MM-DD>" last_updated = "<YYYY-MM-DD>"
[blocked_by] [blocked_by]
@@ -818,9 +818,9 @@ When the implementing agent encounters a decision not covered by the plan:
1. **If the decision is purely cosmetic** (e.g., variable naming, comment placement, exact spacing): pick the option that matches the surrounding code style. Document the choice in the commit message. 1. **If the decision is purely cosmetic** (e.g., variable naming, comment placement, exact spacing): pick the option that matches the surrounding code style. Document the choice in the commit message.
2. **If the decision affects the architecture** (e.g., the spec's data model doesn't fit the code; the plan's approach doesn't compile; an external library doesn't behave as expected): **STOP. Do not commit. Report to the Tier 2 Tech Lead.** The lead will either: 2. **If the decision affects the architecture** (e.g., the spec's data model doesn't fit the code; the plan's approach doesn't compile; an external library doesn't behave as expected): **STOP. Do not commit. Report to the Tier 2 Tech Lead.** The lead will either:
- Update the spec to match the new constraint - Update the spec to match the new constraint
- Add a clarifying task to the plan - Add a clarifying task to the plan
- Defer the work to a follow-up track - Defer the work to a follow-up track
3. **If the decision is a regression** (e.g., the plan's code works but introduces a known bug, or fails a test the plan didn't anticipate): **STOP and report.** Don't ship a known regression to save time. The lead will decide whether to fix forward or roll back. 3. **If the decision is a regression** (e.g., the plan's code works but introduces a known bug, or fails a test the plan didn't anticipate): **STOP and report.** Don't ship a known regression to save time. The lead will decide whether to fix forward or roll back.
**The principle: small decisions, decide yourself. Large decisions, escalate.** The boundary is "does this decision require a new spec or plan update?" **The principle: small decisions, decide yourself. Large decisions, escalate.** The boundary is "does this decision require a new spec or plan update?"
@@ -913,20 +913,20 @@ This section extends the existing workflow with the patterns surfaced by the `na
``` ```
- [ ] tests/test_knowledge_store.py: 5+ tests for the 7-category schema - [ ] tests/test_knowledge_store.py: 5+ tests for the 7-category schema
- [ ] parse_harvest_json: 7 categories; rows must be lists - [ ] parse_harvest_json: 7 categories; rows must be lists
- [ ] parse_harvest_json: rejects prose - [ ] parse_harvest_json: rejects prose
- [ ] parse_harvest_json: tolerates ```json ... ``` code-fence - [ ] parse_harvest_json: tolerates ```json ... ``` code-fence
- [ ] parse_harvest_json: rejects non-dict payloads - [ ] parse_harvest_json: rejects non-dict payloads
- [ ] regenerate_digest: 4KB cap; truncation with note - [ ] regenerate_digest: 4KB cap; truncation with note
- [ ] tests/test_knowledge_harvest.py: 8+ tests for the pipeline - [ ] tests/test_knowledge_harvest.py: 8+ tests for the pipeline
- [ ] classify (live/user-kept/prune/harvest/keep) - [ ] classify (live/user-kept/prune/harvest/keep)
- [ ] merge_harvest per category - [ ] merge_harvest per category
- [ ] per-file knowledge: existing-file branch - [ ] per-file knowledge: existing-file branch
- [ ] per-file knowledge: missing-file branch - [ ] per-file knowledge: missing-file branch
- [ ] ledger dedup (sha256-of-content) - [ ] ledger dedup (sha256-of-content)
- [ ] retry budget (2 attempts) - [ ] retry budget (2 attempts)
- [ ] "too-large" budget guard (1MB) - [ ] "too-large" budget guard (1MB)
- [ ] "delete to turn off" regeneration - [ ] "delete to turn off" regeneration
``` ```
### The cache ordering TDD protocol ### The cache ordering TDD protocol
@@ -935,17 +935,17 @@ This section extends the existing workflow with the patterns surfaced by the `na
``` ```
- [ ] tests/test_aggregate_caching.py: the byte-comparison test - [ ] tests/test_aggregate_caching.py: the byte-comparison test
- [ ] first N chars are identical across turns of the same discussion - [ ] first N chars are identical across turns of the same discussion
- [ ] N = aggregate.stable_prefix_length(ctrl) - [ ] N = aggregate.stable_prefix_length(ctrl)
- [ ] failure modes: new layer in wrong position, volatile input leak - [ ] failure modes: new layer in wrong position, volatile input leak
- [ ] tests/test_cache_state.py: 3+ tests for the cache state machine - [ ] tests/test_cache_state.py: 3+ tests for the cache state machine
- [ ] per-provider TTL defaults - [ ] per-provider TTL defaults
- [ ] DiscussionCacheState lifecycle - [ ] DiscussionCacheState lifecycle
- [ ] invalidate + regeneration - [ ] invalidate + regeneration
- [ ] tests/test_gui_caching.py: 3+ live_gui tests for the "Caching" panel - [ ] tests/test_gui_caching.py: 3+ live_gui tests for the "Caching" panel
- [ ] panel renders provider summaries - [ ] panel renders provider summaries
- [ ] invalidate button - [ ] invalidate button
- [ ] per-discussion disable/enable - [ ] per-discussion disable/enable
``` ```
### The compaction TDD protocol ### The compaction TDD protocol
@@ -954,16 +954,16 @@ This section extends the existing workflow with the patterns surfaced by the `na
``` ```
- [ ] tests/test_run_discussion_compaction.py: 10+ tests - [ ] tests/test_run_discussion_compaction.py: 10+ tests
- [ ] compact preserves decisions - [ ] compact preserves decisions
- [ ] compact preserves constraints - [ ] compact preserves constraints
- [ ] compact preserves failures - [ ] compact preserves failures
- [ ] compact preserves artifact refs - [ ] compact preserves artifact refs
- [ ] compact removes duplicates - [ ] compact removes duplicates
- [ ] compact replaces chronology with state - [ ] compact replaces chronology with state
- [ ] compact is substantially smaller - [ ] compact is substantially smaller
- [ ] compact preserves capability - [ ] compact preserves capability
- [ ] compact returns 12-section structure - [ ] compact returns 12-section structure
- [ ] compact continues until self-review passes - [ ] compact continues until self-review passes
``` ```
### The RAG discipline TDD protocol ### The RAG discipline TDD protocol
@@ -972,10 +972,10 @@ This section extends the existing workflow with the patterns surfaced by the `na
``` ```
- [ ] tests/test_rag_discipline.py: 4+ tests - [ ] tests/test_rag_discipline.py: 4+ tests
- [ ] RAG disabled: no {rag-context} block - [ ] RAG disabled: no {rag-context} block
- [ ] RAG results have provenance (file path + chunk) - [ ] RAG results have provenance (file path + chunk)
- [ ] RAG results do not mutate disc_entries - [ ] RAG results do not mutate disc_entries
- [ ] RAG failure returns empty (graceful) - [ ] RAG failure returns empty (graceful)
``` ```
See `conductor/code_styleguides/knowledge_artifacts.md`, `cache_friendly_context.md`, `rag_integration_discipline.md` for the canonical styleguides. See `conductor/code_styleguides/knowledge_artifacts.md`, `cache_friendly_context.md`, `rag_integration_discipline.md` for the canonical styleguides.
+129 -129
View File
@@ -14,7 +14,7 @@ This documentation suite provides comprehensive technical reference for the Manu
| Guide | Contents | | Guide | Contents |
|---|---| |---|---|
| [Architecture](guide_architecture.md) | Thread domains (GUI Main, Asyncio Worker, HookServer, Ad-hoc), cross-thread data structures (AsyncEventQueue, Guarded Lists, Condition-Variable Dialogs), event system (EventEmitter, SyncEventQueue, UserRequestEvent), application lifetime (boot sequence, shutdown sequence), task pipeline (producer-consumer synchronization), Execution Clutch (HITL mechanism with ConfirmDialog, MMAApprovalDialog, MMASpawnApprovalDialog), AI client multi-provider architecture (Gemini SDK, Anthropic, DeepSeek, Gemini CLI, MiniMax), Anthropic/Gemini caching strategies (4-breakpoint system, server-side TTL), context refresh mechanism (mtime-based file re-reading, diff injection), comms logging (JSON-L format), state machines (ai_status, HITL dialog state) | | [Architecture](guide_architecture.md) | Thread domains (GUI Main, Asyncio Worker, HookServer, Ad-hoc), cross-thread data structures (AsyncEventQueue, Guarded Lists, Condition-Variable Dialogs), event system (EventEmitter, SyncEventQueue, UserRequestEvent), application lifetime (boot sequence, shutdown sequence), task pipeline (producer-consumer synchronization), Execution Clutch (HITL mechanism with ConfirmDialog, MMAApprovalDialog, MMASpawnApprovalDialog), AI client multi-provider architecture (Gemini SDK, Anthropic, DeepSeek, MiniMax), Anthropic/Gemini caching strategies (4-breakpoint system, server-side TTL), context refresh mechanism (mtime-based file re-reading, diff injection), comms logging (JSON-L format), state machines (ai_status, HITL dialog state) |
| [Meta-Boundary](guide_meta_boundary.md) | Explicit distinction between the Application's domain (Strict HITL — `gui_2.py`, `ai_client.py`, `multi_agent_conductor.py`, `dag_engine.py`) and the **Meta-Tooling** domain (the OpenCode Task tool with `.opencode/agents/*` tier prompts, `.gemini/`, `.claude/`, plus the legacy `scripts/mma_exec.py` / `scripts/claude_mma_exec.py` / `scripts/tool_call.py` / `scripts/mcp_server.py` for backward compatibility), preventing feature bleed and safety bypasses via shared bridges like `mcp_client.py`. Documents the Inter-Domain Bridges (`cli_tool_bridge.py`, `claude_tool_bridge.py`) and the `GEMINI_CLI_HOOK_CONTEXT` environment variable. **Note (2026-06-27):** the legacy `mma_exec.py` / `claude_mma_exec.py` are DEPRECATED for meta-tooling sub-agent delegation; the OpenCode Task tool is the canonical mechanism. | | [Meta-Boundary](guide_meta_boundary.md) | Explicit distinction between the Application's domain (Strict HITL — `gui_2.py`, `ai_client.py`, `multi_agent_conductor.py`, `dag_engine.py`) and the **Meta-Tooling** domain (the OpenCode Task tool with `.opencode/agents/*` tier prompts, `.gemini/`, `.claude/`, plus the legacy `scripts/mma_exec.py` / `scripts/claude_mma_exec.py` / `scripts/tool_call.py` / `scripts/mcp_server.py` for backward compatibility), preventing feature bleed and safety bypasses via shared bridges like `mcp_client.py`. Documents the Inter-Domain Bridges (`cli_tool_bridge.py`, `claude_tool_bridge.py`) and the `GEMINI_CLI_HOOK_CONTEXT` environment variable. **Note (2026-06-27):** the legacy `mma_exec.py` / `claude_mma_exec.py` are DEPRECATED for meta-tooling sub-agent delegation; the OpenCode Task tool is the canonical mechanism. |
| [Tools & IPC](guide_tools.md) | MCP Bridge 3-layer security model (Allowlist Construction, Path Validation, Resolution Gate), all 45 MCP tool signatures (plus `run_powershell` from `src/shell_runner.py`, for a canonical 46 in `models.AGENT_TOOL_NAMES`) with parameters and behavior (File I/O, AST-Based, Analysis, Network, Runtime, Beads), Hook API GET/POST endpoints with request/response formats, ApiHookClient method reference (Connection Methods, State Query Methods, GUI Manipulation Methods, Polling Methods, HITL Method), `/api/ask` synchronous HITL protocol (blocking request-response over HTTP), session logging (comms.log, toolcalls.log, apihooks.log, clicalls.log, scripts/generated/*.ps1), shell runner (mcp_env.toml configuration, run_powershell function with 60s timeout, qa_callback and patch_callback integration for Tier 4 QA + auto-patch) | | [Tools & IPC](guide_tools.md) | MCP Bridge 3-layer security model (Allowlist Construction, Path Validation, Resolution Gate), all 45 MCP tool signatures (plus `run_powershell` from `src/shell_runner.py`, for a canonical 46 in `models.AGENT_TOOL_NAMES`) with parameters and behavior (File I/O, AST-Based, Analysis, Network, Runtime, Beads), Hook API GET/POST endpoints with request/response formats, ApiHookClient method reference (Connection Methods, State Query Methods, GUI Manipulation Methods, Polling Methods, HITL Method), `/api/ask` synchronous HITL protocol (blocking request-response over HTTP), session logging (comms.log, toolcalls.log, apihooks.log, clicalls.log, scripts/generated/*.ps1), shell runner (mcp_env.toml configuration, run_powershell function with 60s timeout, qa_callback and patch_callback integration for Tier 4 QA + auto-patch) |
| [MMA Orchestration](guide_mma.md) | Ticket/Track/WorkerContext data structures (from `models.py`), DAG engine (TrackDAG class with cycle detection, topological sort, cascade_blocks; ExecutionEngine class with tick-based state machine), ConductorEngine execution loop (run method, _push_state for state broadcast, parse_json_tickets for ingestion), Tier 2 ticket generation (generate_tickets, topological_sort), Tier 3 worker lifecycle (run_worker_lifecycle with Context Amnesia, AST skeleton injection, HITL clutch integration via confirm_spawn and confirm_execution), Tier 4 QA integration (run_tier4_analysis, run_tier4_patch_callback), token firewalling (tier_usage tracking, model escalation), track state persistence (TrackState, save_track_state, load_track_state, get_all_tracks) | | [MMA Orchestration](guide_mma.md) | Ticket/Track/WorkerContext data structures (from `models.py`), DAG engine (TrackDAG class with cycle detection, topological sort, cascade_blocks; ExecutionEngine class with tick-based state machine), ConductorEngine execution loop (run method, _push_state for state broadcast, parse_json_tickets for ingestion), Tier 2 ticket generation (generate_tickets, topological_sort), Tier 3 worker lifecycle (run_worker_lifecycle with Context Amnesia, AST skeleton injection, HITL clutch integration via confirm_spawn and confirm_execution), Tier 4 QA integration (run_tier4_analysis, run_tier4_patch_callback), token firewalling (tier_usage tracking, model escalation), track state persistence (TrackState, save_track_state, load_track_state, get_all_tracks) |
@@ -31,12 +31,12 @@ This documentation suite provides comprehensive technical reference for the Manu
| [Testing](guide_testing.md) | 322 test files, 5 test categories (unit, integration, live_gui, perf, simulation), 7 conftest fixtures (`isolate_workspace`, `reset_paths`, `reset_ai_client`, `vlogger`, `kill_process_tree`, `mock_app`, `live_gui` session-scoped), Hook API testing pattern, Puppeteer pattern for MMA simulation, mock provider strategy, opt-in clean install test, opt-in docker test, coverage targets, anti-patterns (no arbitrary core mocking, artifact isolation to `tests/artifacts/`), early-render C-level crash pattern (`_ini_capture_ready` defer-not-catch for `imgui.save_ini_settings_to_memory`), live_gui authoring contract (wait-for-ready pattern over `time.sleep`, narrow test paths over kitchen-sink `render_main_interface` mocks), test-ordering sensitivity (session-scoped fixture) | | [Testing](guide_testing.md) | 322 test files, 5 test categories (unit, integration, live_gui, perf, simulation), 7 conftest fixtures (`isolate_workspace`, `reset_paths`, `reset_ai_client`, `vlogger`, `kill_process_tree`, `mock_app`, `live_gui` session-scoped), Hook API testing pattern, Puppeteer pattern for MMA simulation, mock provider strategy, opt-in clean install test, opt-in docker test, coverage targets, anti-patterns (no arbitrary core mocking, artifact isolation to `tests/artifacts/`), early-render C-level crash pattern (`_ini_capture_ready` defer-not-catch for `imgui.save_ini_settings_to_memory`), live_gui authoring contract (wait-for-ready pattern over `time.sleep`, narrow test paths over kitchen-sink `render_main_interface` mocks), test-ordering sensitivity (session-scoped fixture) |
| [Themes](guide_themes.md) | TOML-based theming system: file layout (`themes/<name>.toml` global + `project_themes.toml` per-project), schema (`syntax_palette` + `[colors]` table with `imgui.Col_` snake_case keys), 4-syntax-palette upstream limit (`imgui-bundle` ships `dark`/`light`/`mariana`/`retro_blue` only), built-in vs TOML palette dispatch, `load_themes_from_disk` / `get_syntax_palette_for_theme` / `apply_syntax_palette` public API, hot-reload behavior, color-callable convention (`C_LBL()` / `C_VAL()` for theme-aware helpers) | | [Themes](guide_themes.md) | TOML-based theming system: file layout (`themes/<name>.toml` global + `project_themes.toml` per-project), schema (`syntax_palette` + `[colors]` table with `imgui.Col_` snake_case keys), 4-syntax-palette upstream limit (`imgui-bundle` ships `dark`/`light`/`mariana`/`retro_blue` only), built-in vs TOML palette dispatch, `load_themes_from_disk` / `get_syntax_palette_for_theme` / `apply_syntax_palette` public API, hot-reload behavior, color-callable convention (`C_LBL()` / `C_VAL()` for theme-aware helpers) |
| [GUI Main](guide_gui_2.md) | `src/gui_2.py` reference: App class lifecycle, ~90 module-level render functions (UI Delegation Pattern), immgui immediate-mode rendering, Multi-Viewport docks, panel registry, command palette integration, ImGuiScope context managers, hot reload support, key bindings (Ctrl+Shift+P, Ctrl+Alt+R, Ctrl+Z/Y), `_capture_workspace_profile` defer-not-catch pattern (line 813-841, `_ini_capture_ready` flag for `imgui.save_ini_settings_to_memory`), theme color-callable pattern (e.g. `DIR_COLORS`/`KIND_COLORS` dicts store `C_VAL` not `C_VAL()` and are called at use site), `__getattr__` ui_ attrs hasattr-guard (bcdc26d0 silent-None fix), `_LazyModule` / `_FiledialogStub` lazy import proxies, `startup_profiler` + `render_warmup_status_indicator` integration, native `_detect_refresh_rate_win32` (ctypes.EnumDisplaySettingsW) | | [GUI Main](guide_gui_2.md) | `src/gui_2.py` reference: App class lifecycle, ~90 module-level render functions (UI Delegation Pattern), immgui immediate-mode rendering, Multi-Viewport docks, panel registry, command palette integration, ImGuiScope context managers, hot reload support, key bindings (Ctrl+Shift+P, Ctrl+Alt+R, Ctrl+Z/Y), `_capture_workspace_profile` defer-not-catch pattern (line 813-841, `_ini_capture_ready` flag for `imgui.save_ini_settings_to_memory`), theme color-callable pattern (e.g. `DIR_COLORS`/`KIND_COLORS` dicts store `C_VAL` not `C_VAL()` and are called at use site), `__getattr__` ui_ attrs hasattr-guard (bcdc26d0 silent-None fix), `_LazyModule` / `_FiledialogStub` lazy import proxies, `startup_profiler` + `render_warmup_status_indicator` integration, native `_detect_refresh_rate_win32` (ctypes.EnumDisplaySettingsW) |
| [AI Client](guide_ai_client.md) | `src/ai_client.py` reference: multi-provider LLM singleton (8 providers: gemini, anthropic, gemini_cli, deepseek, minimax, qwen, grok, llama), async dispatch with `asyncio.gather`, threading.local for source tier tagging, context caching (Anthropic ephemeral + Gemini explicit), system prompt assembly, error interception for Tier 4 QA, inlined `VendorCapabilities` registry (moved from the deleted `src/vendor_capabilities.py`), `Result[str]`-returning `send()` public API | | [AI Client](guide_ai_client.md) | `src/ai_client.py` reference: multi-provider LLM singleton (7 providers: gemini, anthropic, deepseek, minimax, qwen, grok, llama), async dispatch with `asyncio.gather`, threading.local for source tier tagging, context caching (Anthropic ephemeral + Gemini explicit), system prompt assembly, error interception for Tier 4 QA, inlined `VendorCapabilities` registry (moved from the deleted `src/vendor_capabilities.py`), `Result[str]`-returning `send()` public API |
| [API Hooks](guide_api_hooks.md) | `src/api_hooks.py` + `src/api_hook_client.py` reference: HookServer on `127.0.0.1:8999`, ApiHookClient Python wrapper, 8+ endpoints (`/status`, `/api/gui`, `/api/ask`, `/api/gui/mma_status`, `/api/performance`, `/api/comms`, `/api/diagnostics`), Remote Confirmation Protocol via `/api/ask` (synchronous blocking HITL), `custom_callback` action for invoking any registered App method | | [API Hooks](guide_api_hooks.md) | `src/api_hooks.py` + `src/api_hook_client.py` reference: HookServer on `127.0.0.1:8999`, ApiHookClient Python wrapper, 8+ endpoints (`/status`, `/api/gui`, `/api/ask`, `/api/gui/mma_status`, `/api/performance`, `/api/comms`, `/api/diagnostics`), Remote Confirmation Protocol via `/api/ask` (synchronous blocking HITL), `custom_callback` action for invoking any registered App method |
| [MCP Client](guide_mcp_client.md) | `src/mcp_client.py` reference: 45 native tools (File I/O, Python AST, C/C++ AST, Analysis, Network, Runtime, Beads), 3-layer security model (Allowlist Construction, Path Validation, Resolution Gate), `dispatch()`/`async_dispatch()` entry points, ExternalMCPManager for external MCP servers (Stdio + SSE), JSON-RPC 2.0 engine, public API, configuration | | [MCP Client](guide_mcp_client.md) | `src/mcp_client.py` reference: 45 native tools (File I/O, Python AST, C/C++ AST, Analysis, Network, Runtime, Beads), 3-layer security model (Allowlist Construction, Path Validation, Resolution Gate), `dispatch()`/`async_dispatch()` entry points, ExternalMCPManager for external MCP servers (Stdio + SSE), JSON-RPC 2.0 engine, public API, configuration |
| [App Controller](guide_app_controller.md) | `src/app_controller.py` reference: headless orchestrator owning AppState and all subsystem managers (PresetManager, PersonaManager, ContextPresetManager, ToolPresetManager, ToolBiasEngine, RAGEngine, HistoryManager, WorkspaceManager, HookServer, HotReloader, PathManager), `_predefined_callbacks` and `_gettable_fields` registries for Hook API, SyncEventQueue bridge, preset/persona/context coordination, headless mode | | [App Controller](guide_app_controller.md) | `src/app_controller.py` reference: headless orchestrator owning AppState and all subsystem managers (PresetManager, PersonaManager, ContextPresetManager, ToolPresetManager, ToolBiasEngine, RAGEngine, HistoryManager, WorkspaceManager, HookServer, HotReloader, PathManager), `_predefined_callbacks` and `_gettable_fields` registries for Hook API, SyncEventQueue bridge, preset/persona/context coordination, headless mode |
| [MMA Engine](guide_multi_agent_conductor.md) | `src/multi_agent_conductor.py` + `src/dag_engine.py` reference: TrackDAG with cycle detection (iterative DFS) and topological sort (Kahn's variant), ExecutionEngine with Auto-Queue / Step Mode state machine, MultiAgentConductor with WorkerPool (configurable concurrency, default 4), the WorkerPool's internal `run_worker_lifecycle` subprocess template (NOT the meta-tooling `mma_exec.py` — that's deprecated; see `guide_meta_boundary.md`), parse_plan_md utility (now in `src/mma.py`), Beads mode delegation | | [MMA Engine](guide_multi_agent_conductor.md) | `src/multi_agent_conductor.py` + `src/dag_engine.py` reference: TrackDAG with cycle detection (iterative DFS) and topological sort (Kahn's variant), ExecutionEngine with Auto-Queue / Step Mode state machine, MultiAgentConductor with WorkerPool (configurable concurrency, default 4), the WorkerPool's internal `run_worker_lifecycle` subprocess template (NOT the meta-tooling `mma_exec.py` — that's deprecated; see `guide_meta_boundary.md`), parse_plan_md utility (now in `src/mma.py`), Beads mode delegation |
| [Data Models](guide_models.md) | `src/models.py` is now a ~1.5KB legacy re-export shim (`Metadata = TrackMetadata` alias + `PROVIDERS` lazy `__getattr__`). Data models moved to per-system files per `module_taxonomy_refactor_20260627`: `src/mma.py` (TrackMetadata, Ticket, Track, WorkerContext), `src/project_files.py` (FileItem), `src/type_aliases.py` (typed boundary + per-aggregate dataclasses: Metadata, CommsLogEntry, HistoryMessage, ToolDefinition, SessionInsights, DiscussionSettings, CustomSlice, MMAUsageStats, ProviderPayload, UIPanelConfig, PathInfo, FileItemsDiff, JsonPrimitive/JsonValue), `src/mcp_tool_specs.py` (typed ToolSpec registry, 45 tools), `src/result_types.py` (Result[T], ErrorInfo, ErrorKind). `VendorCapabilities` lives in `src/ai_client.py` `#region: Vendor Capabilities`. `PROVIDERS` constant in `src/ai_client.py` (8 providers: gemini, anthropic, gemini_cli, deepseek, minimax, qwen, grok, llama). | | [Data Models](guide_models.md) | `src/models.py` is now a ~1.5KB legacy re-export shim (`Metadata = TrackMetadata` alias + `PROVIDERS` lazy `__getattr__`). Data models moved to per-system files per `module_taxonomy_refactor_20260627`: `src/mma.py` (TrackMetadata, Ticket, Track, WorkerContext), `src/project_files.py` (FileItem), `src/type_aliases.py` (typed boundary + per-aggregate dataclasses: Metadata, CommsLogEntry, HistoryMessage, ToolDefinition, SessionInsights, DiscussionSettings, CustomSlice, MMAUsageStats, ProviderPayload, UIPanelConfig, PathInfo, FileItemsDiff, JsonPrimitive/JsonValue), `src/mcp_tool_specs.py` (typed ToolSpec registry, 45 tools), `src/result_types.py` (Result[T], ErrorInfo, ErrorKind). `VendorCapabilities` lives in `src/ai_client.py` `#region: Vendor Capabilities`. `PROVIDERS` constant in `src/ai_client.py` (7 providers: gemini, anthropic, deepseek, minimax, qwen, grok, llama). |
| [Discussions](guide_discussions.md) | The Discussion system: 23-operation matrix A1-A7 (per-entry) + B1-B11 (discussion-level) + C1-C5 (undo/redo), Take naming convention (`<base>_take_<n>`), branching at any entry (`project_manager.branch_discussion`), promotion to top-level (`project_manager.promote_take`), user-managed role list (`app.disc_roles`), per-role filter linked to MMA persona focus, `_disc_entries_lock` thread-safety contract, Hook API session endpoints | | [Discussions](guide_discussions.md) | The Discussion system: 23-operation matrix A1-A7 (per-entry) + B1-B11 (discussion-level) + C1-C5 (undo/redo), Take naming convention (`<base>_take_<n>`), branching at any entry (`project_manager.branch_discussion`), promotion to top-level (`project_manager.promote_take`), user-managed role list (`app.disc_roles`), per-role filter linked to MMA persona focus, `_disc_entries_lock` thread-safety contract, Hook API session endpoints |
| [State Lifecycle](guide_state_lifecycle.md) | Undo/redo via `HistoryManager` + `UISnapshot` (13 captured fields, 100-snapshot capacity, debounced change detection at render frame), reset flow (`_handle_reset_session` — clears 30+ fields, replaces project, preserves `active_project_path` per the 2026-06-08 regression fix), `App.__getattr__`/`__setattr__` state delegation to Controller, 8-thread io_pool with 11 lock-protected regions (per `IO_POOL_MAX_WORKERS = 8` in `src/io_pool.py:20`; bumped 4→8 in 4a338486 on 2026-06-06), hot-reload integration | | [State Lifecycle](guide_state_lifecycle.md) | Undo/redo via `HistoryManager` + `UISnapshot` (13 captured fields, 100-snapshot capacity, debounced change detection at render frame), reset flow (`_handle_reset_session` — clears 30+ fields, replaces project, preserves `active_project_path` per the 2026-06-08 regression fix), `App.__getattr__`/`__setattr__` state delegation to Controller, 8-thread io_pool with 11 lock-protected regions (per `IO_POOL_MAX_WORKERS = 8` in `src/io_pool.py:20`; bumped 4→8 in 4a338486 on 2026-06-06), hot-reload integration |
| [Context Aggregation](guide_context_aggregation.md) | The `aggregate.py` (518-line) pipeline: 3 aggregation strategies (`auto`/`summarize`/`full`), 7 per-file view modes (`full`/`summary`/`skeleton`/`outline`/`masked`/`custom`/`none`), full `FileItem` schema (9 fields + `__post_init__` normalizer), `ContextPreset` schema and `ContextPresetManager`, Tier 3 worker variant (`build_tier3_context` with FuzzyAnchor re-resolution and focus-file handling), `force_full`/`auto_aggregate` short-circuits, output file numbering, cache strategy (static prefix + dynamic history) | | [Context Aggregation](guide_context_aggregation.md) | The `aggregate.py` (518-line) pipeline: 3 aggregation strategies (`auto`/`summarize`/`full`), 7 per-file view modes (`full`/`summary`/`skeleton`/`outline`/`masked`/`custom`/`none`), full `FileItem` schema (9 fields + `__post_init__` normalizer), `ContextPreset` schema and `ContextPresetManager`, Tier 3 worker variant (`build_tier3_context` with FuzzyAnchor re-resolution and focus-file handling), `force_full`/`auto_aggregate` short-circuits, output file numbering, cache strategy (static prefix + dynamic history) |
@@ -85,8 +85,8 @@ Controls what context is compiled and sent to the AI.
- **Base Dir**: Root directory for path resolution and MCP tool constraints. - **Base Dir**: Root directory for path resolution and MCP tool constraints.
- **Paths**: Explicit files or wildcard globs (`src/**/*.py`). - **Paths**: Explicit files or wildcard globs (`src/**/*.py`).
- **File Flags**: - **File Flags**:
- **Auto-Aggregate**: Include in context compilation. - **Auto-Aggregate**: Include in context compilation.
- **Force Full**: Bypass summary-only mode for this file. - **Force Full**: Bypass summary-only mode for this file.
- **Cache Indicator**: Green dot (●) indicates file is in provider's context cache. - **Cache Indicator**: Green dot (●) indicates file is in provider's context cache.
### Discussion Hub ### Discussion Hub
@@ -101,7 +101,7 @@ Manages conversational branches to prevent context poisoning across tasks.
### AI Settings Panel ### AI Settings Panel
- **Provider**: Switch between API backends (Gemini, Anthropic, DeepSeek, Gemini CLI, MiniMax). - **Provider**: Switch between API backends (Gemini, Anthropic, DeepSeek, MiniMax).
- **Model**: Select from available models for the current provider. - **Model**: Select from available models for the current provider.
- **Fetch Models**: Queries the active provider for the latest model list. - **Fetch Models**: Queries the active provider for the latest model list.
- **Temperature / Max Tokens**: Generation parameters. - **Temperature / Max Tokens**: Generation parameters.
@@ -324,127 +324,127 @@ EXPANDED = "${HOME}/subdir"
``` ```
manual_slop/ manual_slop/
├── conductor/ # Conductor system ├── conductor/ # Conductor system
├── tracks/ # Track directories │ ├── tracks/ # Track directories
└── <track_id>/ # Per-track files └── <track_id>/ # Per-track files
├── spec.md ├── spec.md
├── plan.md ├── plan.md
├── metadata.json ├── metadata.json
└── state.toml └── state.toml
├── archive/ # Completed tracks │ ├── archive/ # Completed tracks
├── product.md # Product definition │ ├── product.md # Product definition
├── product-guidelines.md │ ├── product-guidelines.md
├── tech-stack.md │ ├── tech-stack.md
├── workflow.md │ ├── workflow.md
├── index.md │ ├── index.md
└── edit_workflow.md │ └── edit_workflow.md
├── docs/ # Deep-dive documentation (27 guides + specs/plans) ├── docs/ # Deep-dive documentation (27 guides + specs/plans)
├── guide_ai_client.md # Multi-provider LLM client │ ├── guide_ai_client.md # Multi-provider LLM client
├── guide_api_hooks.md # HookServer + ApiHookClient │ ├── guide_api_hooks.md # HookServer + ApiHookClient
├── guide_app_controller.md # Headless AppController │ ├── guide_app_controller.md # Headless AppController
├── guide_architecture.md # Threading, event system, state machines │ ├── guide_architecture.md # Threading, event system, state machines
├── guide_beads.md # Beads/Dolt issue tracking │ ├── guide_beads.md # Beads/Dolt issue tracking
├── guide_command_palette.md # Command palette + 33 registered commands │ ├── guide_command_palette.md # Command palette + 33 registered commands
├── guide_context_aggregation.md # aggregate.py pipeline (strategies + view modes) │ ├── guide_context_aggregation.md # aggregate.py pipeline (strategies + view modes)
├── guide_context_curation.md # Granular AST control + Fuzzy Anchor slices │ ├── guide_context_curation.md # Granular AST control + Fuzzy Anchor slices
├── guide_discussions.md # Discussion system + A1-A7 matrix │ ├── guide_discussions.md # Discussion system + A1-A7 matrix
├── guide_docker_deployment.md # Docker + Gitea registry deployment │ ├── guide_docker_deployment.md # Docker + Gitea registry deployment
├── guide_gui_2.md # Main ImGui interface (App class, render functions) │ ├── guide_gui_2.md # Main ImGui interface (App class, render functions)
├── guide_hot_reload.md # State-preserving module reloading │ ├── guide_hot_reload.md # State-preserving module reloading
├── guide_mcp_client.md # 45 MCP tools + 3-layer security │ ├── guide_mcp_client.md # 45 MCP tools + 3-layer security
├── guide_meta_boundary.md # Application vs Meta-Tooling split │ ├── guide_meta_boundary.md # Application vs Meta-Tooling split
├── guide_mma.md # 4-Tier MMA concepts │ ├── guide_mma.md # 4-Tier MMA concepts
├── guide_models.md # Data model registry │ ├── guide_models.md # Data model registry
├── guide_multi_agent_conductor.md # ConductorEngine + TrackDAG + WorkerPool │ ├── guide_multi_agent_conductor.md # ConductorEngine + TrackDAG + WorkerPool
├── guide_nerv_theme.md # NERV Tactical Console theme │ ├── guide_nerv_theme.md # NERV Tactical Console theme
├── guide_personas.md # Unified agent profile system │ ├── guide_personas.md # Unified agent profile system
├── guide_rag.md # RAG subsystem (ChromaDB + embeddings) │ ├── guide_rag.md # RAG subsystem (ChromaDB + embeddings)
├── guide_shaders_and_window.md # Shader injection + custom window frame │ ├── guide_shaders_and_window.md # Shader injection + custom window frame
├── guide_simulations.md # Test framework + Puppeteer pattern │ ├── guide_simulations.md # Test framework + Puppeteer pattern
├── guide_state_lifecycle.md # Undo/redo + state delegation │ ├── guide_state_lifecycle.md # Undo/redo + state delegation
├── guide_testing.md # 322 test files + 7 conftest fixtures │ ├── guide_testing.md # 322 test files + 7 conftest fixtures
├── guide_themes.md # Multi-theme TOML system │ ├── guide_themes.md # Multi-theme TOML system
├── guide_tools.md # MCP tools + shell runner │ ├── guide_tools.md # MCP tools + shell runner
├── guide_workspace_profiles.md # Workspace profile save/load │ ├── guide_workspace_profiles.md # Workspace profile save/load
├── Readme.md │ ├── Readme.md
├── MMA_Support/ # Legacy MMA reference (deprecated) │ ├── MMA_Support/ # Legacy MMA reference (deprecated)
├── reports/ # Phase 5 reports │ ├── reports/ # Phase 5 reports
└── superpowers/ # Specs and plans for design work │ └── superpowers/ # Specs and plans for design work
├── src/ # Core implementation (53 modules) ├── src/ # Core implementation (53 modules)
├── gui_2.py # Primary ImGui interface │ ├── gui_2.py # Primary ImGui interface
├── app_controller.py # Headless controller │ ├── app_controller.py # Headless controller
├── ai_client.py # Multi-provider LLM (Gemini, Anthropic, DeepSeek, MiniMax) │ ├── ai_client.py # Multi-provider LLM (Gemini, Anthropic, DeepSeek, MiniMax)
├── mcp_client.py # 45 MCP tools + 1 shell runner (canonical 46) with 3-layer security │ ├── mcp_client.py # 45 MCP tools + 1 shell runner (canonical 46) with 3-layer security
├── api_hooks.py # HookServer REST API on :8999 │ ├── api_hooks.py # HookServer REST API on :8999
├── api_hook_client.py # Python client for the Hook API │ ├── api_hook_client.py # Python client for the Hook API
├── multi_agent_conductor.py # ConductorEngine │ ├── multi_agent_conductor.py # ConductorEngine
├── dag_engine.py # TrackDAG + ExecutionEngine │ ├── dag_engine.py # TrackDAG + ExecutionEngine
├── models.py # Ticket, Track, WorkerContext, etc. │ ├── models.py # Ticket, Track, WorkerContext, etc.
├── events.py # EventEmitter, SyncEventQueue │ ├── events.py # EventEmitter, SyncEventQueue
├── project_manager.py # TOML persistence, discussion management │ ├── project_manager.py # TOML persistence, discussion management
├── session_logger.py # JSON-L + markdown audit trails │ ├── session_logger.py # JSON-L + markdown audit trails
├── rag_engine.py # RAG (ChromaDB + embedding providers) │ ├── rag_engine.py # RAG (ChromaDB + embedding providers)
├── beads_client.py # Beads/Dolt issue tracking client │ ├── beads_client.py # Beads/Dolt issue tracking client
├── hot_reloader.py # State-preserving module reloader │ ├── hot_reloader.py # State-preserving module reloader
├── personas.py # Unified agent profile manager │ ├── personas.py # Unified agent profile manager
├── presets.py # System prompt preset manager │ ├── presets.py # System prompt preset manager
├── context_presets.py # Context composition preset manager │ ├── context_presets.py # Context composition preset manager
├── tool_presets.py # Tool preset manager │ ├── tool_presets.py # Tool preset manager
├── tool_bias.py # Tool bias engine │ ├── tool_bias.py # Tool bias engine
├── command_palette.py # Command palette + fuzzy matcher │ ├── command_palette.py # Command palette + fuzzy matcher
├── commands.py # 33 registered commands │ ├── commands.py # 33 registered commands
├── workspace_manager.py # Workspace profile save/load │ ├── workspace_manager.py # Workspace profile save/load
├── theme_2.py # Theme system (palette/font/etc.) │ ├── theme_2.py # Theme system (palette/font/etc.)
├── theme_nerv.py # NERV Tactical Console theme │ ├── theme_nerv.py # NERV Tactical Console theme
├── theme_nerv_fx.py # NERV FX (scanlines, flicker, alert) │ ├── theme_nerv_fx.py # NERV FX (scanlines, flicker, alert)
├── shell_runner.py # PowerShell execution with 60s timeout + qa_callback + patch_callback │ ├── shell_runner.py # PowerShell execution with 60s timeout + qa_callback + patch_callback
├── file_cache.py # ASTParser (tree-sitter) │ ├── file_cache.py # ASTParser (tree-sitter)
├── summarize.py # Heuristic file summaries │ ├── summarize.py # Heuristic file summaries
├── outline_tool.py # Hierarchical code outline │ ├── outline_tool.py # Hierarchical code outline
├── fuzzy_anchor.py # Fuzzy anchor slice algorithm │ ├── fuzzy_anchor.py # Fuzzy anchor slice algorithm
├── history.py # Undo/redo HistoryManager │ ├── history.py # Undo/redo HistoryManager
├── imgui_scopes.py # ImGui context managers │ ├── imgui_scopes.py # ImGui context managers
├── performance_monitor.py # FPS/CPU tracking │ ├── performance_monitor.py # FPS/CPU tracking
├── log_registry.py # Session metadata │ ├── log_registry.py # Session metadata
├── log_pruner.py # Automated log cleanup │ ├── log_pruner.py # Automated log cleanup
├── paths.py # Centralized path resolution │ ├── paths.py # Centralized path resolution
├── cost_tracker.py # Token cost estimation │ ├── cost_tracker.py # Token cost estimation
├── gemini_cli_adapter.py # CLI subprocess adapter │ ├── gemini_cli_adapter.py # CLI subprocess adapter
├── mma_prompts.py # Tier-specific system prompts │ ├── mma_prompts.py # Tier-specific system prompts
├── summary_cache.py # SHA256-keyed summary LRU cache │ ├── summary_cache.py # SHA256-keyed summary LRU cache
├── markdown_helper.py # Markdown rendering helpers │ ├── markdown_helper.py # Markdown rendering helpers
├── patch_modal.py # Patch approval modal │ ├── patch_modal.py # Patch approval modal
├── diff_viewer.py # Diff rendering │ ├── diff_viewer.py # Diff rendering
├── external_editor.py # External editor integration │ ├── external_editor.py # External editor integration
├── orchestrator_pm.py # Orchestrator project manager │ ├── orchestrator_pm.py # Orchestrator project manager
├── conductor_tech_lead.py # Tier 2 ticket generation │ ├── conductor_tech_lead.py # Tier 2 ticket generation
├── synthesis_formatter.py # Multi-take synthesis │ ├── synthesis_formatter.py # Multi-take synthesis
├── thinking_parser.py # AI thinking-trace extraction │ ├── thinking_parser.py # AI thinking-trace extraction
└── __init__.py │ └── __init__.py
├── simulation/ # Test simulations ├── simulation/ # Test simulations
├── sim_base.py # BaseSimulation class │ ├── sim_base.py # BaseSimulation class
├── workflow_sim.py # WorkflowSimulator │ ├── workflow_sim.py # WorkflowSimulator
├── user_agent.py # UserSimAgent │ ├── user_agent.py # UserSimAgent
├── sim_context.py # ContextSimulation │ ├── sim_context.py # ContextSimulation
├── sim_execution.py # ExecutionSimulation │ ├── sim_execution.py # ExecutionSimulation
├── sim_ai_settings.py # AISettingsSimulation │ ├── sim_ai_settings.py # AISettingsSimulation
└── sim_tools.py # ToolsSimulation │ └── sim_tools.py # ToolsSimulation
├── tests/ # Test suite (251 files) ├── tests/ # Test suite (251 files)
├── conftest.py # Fixtures (live_gui, isolate_workspace, etc.) │ ├── conftest.py # Fixtures (live_gui, isolate_workspace, etc.)
├── mock_gemini_cli.py # Mock provider for integration tests │ ├── mock_gemini_cli.py # Mock provider for integration tests
├── test_*.py # Unit tests │ ├── test_*.py # Unit tests
├── *_sim.py # Integration tests using live_gui │ ├── *_sim.py # Integration tests using live_gui
├── test_clean_install.py # Opt-in: clones repo and verifies hooks │ ├── test_clean_install.py # Opt-in: clones repo and verifies hooks
├── test_docker_build.py # Opt-in: builds Docker image │ ├── test_docker_build.py # Opt-in: builds Docker image
├── artifacts/ # Git-ignored; test outputs │ ├── artifacts/ # Git-ignored; test outputs
└── logs/ # Git-ignored; live_gui log files │ └── logs/ # Git-ignored; live_gui log files
├── scripts/ # Utility scripts ├── scripts/ # Utility scripts
├── generated/ # AI-generated scripts │ ├── generated/ # AI-generated scripts
├── check_test_toml_paths.py # Audit script (CI gate) │ ├── check_test_toml_paths.py # Audit script (CI gate)
├── docker_build.sh │ ├── docker_build.sh
└── docker_run.sh │ └── docker_run.sh
├── sloppy.py # Main entry point ├── sloppy.py # Main entry point
├── config.toml # Global configuration ├── config.toml # Global configuration
├── manual_slop.toml # Active project config (current) ├── manual_slop.toml # Active project config (current)
└── credentials.toml # API keys (gitignored) └── credentials.toml # API keys (gitignored)
``` ```
+182 -182
View File
@@ -6,14 +6,14 @@
## Overview ## Overview
`src/ai_client.py` (~166KB) is the **unified LLM client** for 8 providers. It abstracts the differences between providers (Gemini, Anthropic, DeepSeek, MiniMax, Gemini CLI, Qwen, Grok, Llama) behind a single `send()` function. `src/ai_client.py` (~166KB) is the **unified LLM client** for 7 providers. It abstracts the differences between providers (Gemini, Anthropic, DeepSeek, MiniMax, Qwen, Grok, Llama) behind a single `send()` function.
The module is a **stateful singleton** — all provider state is held in module-level globals. There is no class wrapping; the module itself is the abstraction layer. The module is a **stateful singleton** — all provider state is held in module-level globals. There is no class wrapping; the module itself is the abstraction layer.
The 8 providers split into 3 API shapes: The 7 providers split into 3 API shapes:
- **Native SDK**: Gemini (google-genai), Anthropic (anthropic), Qwen (DashScope) - **Native SDK**: Gemini (google-genai), Anthropic (anthropic), Qwen (DashScope)
- **OpenAI-compatible**: MiniMax, Grok, Llama (Ollama/OpenRouter/custom), DeepSeek - **OpenAI-compatible**: MiniMax, Grok, Llama (Ollama/OpenRouter/custom), DeepSeek
- **Subprocess**: Gemini CLI - **Subprocess**:
The OpenAI-compatible vendors all call the shared helper in `src/openai_compatible.py` (added 2026-06-06 by the `qwen_llama_grok_integration_20260606` track; see "Shared OpenAI-Compatible Helper" section below). The MiniMax provider's `_send_minimax` was refactored to use this helper (Phase 4 of the same track, 231 → 75 lines, 68% reduction). The OpenAI-compatible vendors all call the shared helper in `src/openai_compatible.py` (added 2026-06-06 by the `qwen_llama_grok_integration_20260606` track; see "Shared OpenAI-Compatible Helper" section below). The MiniMax provider's `_send_minimax` was refactored to use this helper (Phase 4 of the same track, 231 → 75 lines, 68% reduction).
@@ -21,7 +21,7 @@ The OpenAI-compatible vendors all call the shared helper in `src/openai_compatib
## Module-Level Imports ## Module-Level Imports
> **Important:** The provider SDKs are **NOT** imported at module level. `import google.genai`, `import anthropic`, `import openai`, `import dashscope`, and `import fastapi` are heavy (~430-955ms each on cold load) and are now obtained via `src.module_loader._require_warmed("google.genai")` and similar calls, after the `WarmupManager` has loaded them in the background. The module-level globals you see in the State section (`_gemini_client`, `_anthropic_client`, etc.) are typed as `Optional` because they're populated by `_require_warmed()` on first use, not at import time. (Updated 2026-07-02: there are 8 providers, not 5 — the original "5 SDKs" count predated the qwen/grok/llama additions.) > **Important:** The provider SDKs are **NOT** imported at module level. `import google.genai`, `import anthropic`, `import openai`, `import dashscope`, and `import fastapi` are heavy (~430-955ms each on cold load) and are now obtained via `src.module_loader._require_warmed("google.genai")` and similar calls, after the `WarmupManager` has loaded them in the background. The module-level globals you see in the State section (`_gemini_client`, `_anthropic_client`, etc.) are typed as `Optional` because they're populated by `_require_warmed()` on first use, not at import time. (Updated 2026-07-02: there are 7 providers, not 5 — the original "5 SDKs" count predated the qwen/grok/llama additions.)
This change was part of the 2026-06-06 `startup_speedup_20260606` track. Before: `import src.ai_client` took ~1800ms. After: ~161ms. The remaining cost is the bare module skeleton. This change was part of the 2026-06-06 `startup_speedup_20260606` track. Before: `import src.ai_client` took ~1800ms. After: ~161ms. The remaining cost is the bare module skeleton.
@@ -29,19 +29,19 @@ This change was part of the 2026-06-06 `startup_speedup_20260606` track. Before:
``` ```
┌─────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────┐
│ ai_client.send(md_content, user_message, ...) │ ai_client.send(md_content, user_message, ...) │
│ │
│ 1. _send_lock.acquire() — serialize all calls │ 1. _send_lock.acquire() — serialize all calls │
│ 2. Read _provider / _model │ 2. Read _provider / _model │
│ 3. Route to provider-specific _send_<provider>() │ │ 3. Route to provider-specific _send_<provider>() │
│ 4. Return str response │ 4. Return str response │
└─────────────────┬───────────────────────────────┘ └─────────────────┬───────────────────────────────┘
│ dispatches based on _provider │ dispatches based on _provider
┌────────┬─────────┬────────┬──────────┐ ┌────────┬─────────┬────────┬──────────┐
▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
_gemini _anthropic _deepseek _minimax _gemini_cli _gemini _anthropic _deepseek _minimax _gemini_cli
(subprocess) (subprocess)
``` ```
--- ---
@@ -94,18 +94,18 @@ _gemini_cli_adapter: Optional[GeminiCliAdapter] = None
```python ```python
def send( def send(
md_content: str, md_content: str,
user_message: str, user_message: str,
base_dir: str = ".", base_dir: str = ".",
file_items: list[dict] | None = None, file_items: list[dict] | None = None,
discussion_history: str = "", discussion_history: str = "",
stream: bool = False, stream: bool = False,
pre_tool_callback: Optional[Callable] = None, pre_tool_callback: Optional[Callable] = None,
qa_callback: Optional[Callable] = None, qa_callback: Optional[Callable] = None,
enable_tools: bool = True, enable_tools: bool = True,
stream_callback: Optional[Callable] = None, stream_callback: Optional[Callable] = None,
patch_callback: Optional[Callable] = None, patch_callback: Optional[Callable] = None,
rag_engine: Optional[Any] = None, rag_engine: Optional[Any] = None,
) -> Result[str]: ) -> Result[str]:
``` ```
@@ -147,7 +147,7 @@ ai_client.set_model_params(temp=0.7, max_tok=4096, top_p=0.9, trunc_limit=4000)
### Session Management ### Session Management
```python ```python
ai_client.reset_session() # Clears all provider state, history, cache ai_client.reset_session() # Clears all provider state, history, cache
``` ```
### Event Hooks ### Event Hooks
@@ -171,10 +171,10 @@ ai_client.events.on("my_event", my_handler)
### Comms Log ### Comms Log
```python ```python
ai_client._append_comms(direction, kind, payload) # Add entry ai_client._append_comms(direction, kind, payload) # Add entry
ai_client.get_comms_log() # Read all ai_client.get_comms_log() # Read all
ai_client.clear_comms_log() # Clear ai_client.clear_comms_log() # Clear
ai_client.get_token_stats(md_content) # Estimate token usage ai_client.get_token_stats(md_content) # Estimate token usage
``` ```
### Provider Error Taxonomy — Legacy (Pre-Refactor) ### Provider Error Taxonomy — Legacy (Pre-Refactor)
@@ -187,12 +187,12 @@ ai_client.get_token_stats(md_content) # Estimate token usage
```python ```python
class ProviderError(Exception): class ProviderError(Exception):
kind: str # "quota" | "rate_limit" | "auth" | "balance" | "network" | "unknown" kind: str # "quota" | "rate_limit" | "auth" | "balance" | "network" | "unknown"
provider: str provider: str
original: Exception original: Exception
def ui_message(self) -> str: def ui_message(self) -> str:
"""Returns a user-friendly error message.""" """Returns a user-friendly error message."""
``` ```
`ProviderError` was raised by provider-specific `_send_*` functions on failure. `ProviderError` was raised by provider-specific `_send_*` functions on failure.
@@ -210,30 +210,30 @@ All providers follow the same high-level pattern in `_send_*`:
```python ```python
def _send_<provider>(md_content, user_message, ...): def _send_<provider>(md_content, user_message, ...):
for round in range(MAX_TOOL_ROUNDS + 2): # up to 10 rounds for round in range(MAX_TOOL_ROUNDS + 2): # up to 10 rounds
response = provider_api_call(md_content, user_message, history, tools) response = provider_api_call(md_content, user_message, history, tools)
comms_log(direction="IN", kind="response", payload=response) comms_log(direction="IN", kind="response", payload=response)
if not has_function_calls(response): if not has_function_calls(response):
return extract_text(response) return extract_text(response)
for call in response.function_calls: for call in response.function_calls:
if pre_tool_callback and pre_tool_callback(...) is rejected: if pre_tool_callback and pre_tool_callback(...) is rejected:
return rejection_message return rejection_message
tool_result = dispatch(call.name, call.args, base_dir) tool_result = dispatch(call.name, call.args, base_dir)
append_tool_result_to_history(call, tool_result) append_tool_result_to_history(call, tool_result)
# Context refresh: re-read all tracked files (mtime check) # Context refresh: re-read all tracked files (mtime check)
_reread_file_items(file_items) _reread_file_items(file_items)
# Truncate tool outputs at _history_trunc_limit # Truncate tool outputs at _history_trunc_limit
truncate_tool_outputs(history) truncate_tool_outputs(history)
# Cumulative byte check # Cumulative byte check
if cumulative_tool_bytes > 500_000: if cumulative_tool_bytes > 500_000:
inject_warning() inject_warning()
return final_response return final_response
``` ```
The constants: The constants:
@@ -273,7 +273,7 @@ The constants:
- **History trimming**: similar to Anthropic (drop turn pairs at threshold) - **History trimming**: similar to Anthropic (drop turn pairs at threshold)
- **History repair**: `_repair_minimax_history` - **History repair**: `_repair_minimax_history`
### Gemini CLI ###
- **Subprocess adapter**: `GeminiCliAdapter` in `src/gemini_cli_adapter.py` - **Subprocess adapter**: `GeminiCliAdapter` in `src/gemini_cli_adapter.py`
- **Persistent session**: CLI maintains its own session ID - **Persistent session**: CLI maintains its own session ID
@@ -288,9 +288,9 @@ The constants:
```python ```python
if total_in > _GEMINI_MAX_INPUT_TOKENS * 0.4: if total_in > _GEMINI_MAX_INPUT_TOKENS * 0.4:
while len(hist) > 4 and total_in > _GEMINI_MAX_INPUT_TOKENS * 0.3: while len(hist) > 4 and total_in > _GEMINI_MAX_INPUT_TOKENS * 0.3:
hist.pop(0) # Assistant hist.pop(0) # Assistant
hist.pop(0) # User hist.pop(0) # User
``` ```
### Anthropic (180K limit) ### Anthropic (180K limit)
@@ -314,8 +314,8 @@ No built-in trimming (relies on the caller to keep history short).
### Gemini Server-Side Cache ### Gemini Server-Side Cache
```python ```python
_gemini_cache_md_hash: Optional[str] = None # Hash of cached content _gemini_cache_md_hash: Optional[str] = None # Hash of cached content
_gemini_cache_created_at: Optional[float] = None # Monotonic time _gemini_cache_created_at: Optional[float] = None # Monotonic time
``` ```
The cache decision is a 3-way branch on each `_send_gemini` call: The cache decision is a 3-way branch on each `_send_gemini` call:
@@ -344,8 +344,8 @@ After the last tool call in each round, `_reread_file_items(file_items)` checks
2. If unchanged: pass through as-is 2. If unchanged: pass through as-is
3. If changed: re-read content, store `old_content` for diffing, update `mtime` 3. If changed: re-read content, store `old_content` for diffing, update `mtime`
4. Changed files are diffed via `_build_file_diff_text`: 4. Changed files are diffed via `_build_file_diff_text`:
- Files ≤ 200 lines: emit full content - Files ≤ 200 lines: emit full content
- Files > 200 lines with `old_content`: emit `difflib.unified_diff` - Files > 200 lines with `old_content`: emit `difflib.unified_diff`
5. Diff is appended to the last tool's output as `[SYSTEM: FILES UPDATED]\n\n{diff}` 5. Diff is appended to the last tool's output as `[SYSTEM: FILES UPDATED]\n\n{diff}`
6. Stale `[FILES UPDATED]` blocks are stripped from older history turns by `_strip_stale_file_refreshes` 6. Stale `[FILES UPDATED]` blocks are stripped from older history turns by `_strip_stale_file_refreshes`
@@ -359,19 +359,19 @@ For Tier 4: when an error occurs, `qa_callback` may be invoked to get a Tier 4 A
```python ```python
def run_tier4_analysis(stderr: str) -> str: def run_tier4_analysis(stderr: str) -> str:
"""Stateless Tier 4 QA analysis of an error message.""" """Stateless Tier 4 QA analysis of an error message."""
# Uses a dedicated system prompt for error triage # Uses a dedicated system prompt for error triage
# Returns analysis text (root cause, suggested fix) # Returns analysis text (root cause, suggested fix)
# Does NOT modify any code — analysis only # Does NOT modify any code — analysis only
``` ```
For Tier 4 patch generation: For Tier 4 patch generation:
```python ```python
def run_tier4_patch_generation(error: str, file_context: str) -> str: def run_tier4_patch_generation(error: str, file_context: str) -> str:
"""Generate a unified diff patch from an error and file context.""" """Generate a unified diff patch from an error and file context."""
# Returns the patch as a string # Returns the patch as a string
# The caller (typically the patch modal) presents it for human review # The caller (typically the patch modal) presents it for human review
``` ```
--- ---
@@ -416,10 +416,10 @@ def run_tier4_patch_generation(error: str, file_context: str) -> str:
```python ```python
def test_set_provider(): def test_set_provider():
from src import ai_client from src import ai_client
ai_client.set_provider("anthropic", "claude-3-5-sonnet-latest") ai_client.set_provider("anthropic", "claude-3-5-sonnet-latest")
assert ai_client.get_provider() == "anthropic" assert ai_client.get_provider() == "anthropic"
ai_client.reset_session() # Cleanup ai_client.reset_session() # Cleanup
``` ```
### Mocked Tests ### Mocked Tests
@@ -428,12 +428,12 @@ def test_set_provider():
from unittest.mock import patch from unittest.mock import patch
def test_send_routes_to_provider(monkeypatch): def test_send_routes_to_provider(monkeypatch):
with patch.object(ai_client, "_send_anthropic", return_value="mocked") as m: with patch.object(ai_client, "_send_anthropic", return_value="mocked") as m:
ai_client.set_provider("anthropic", "claude-3-5-sonnet-latest") ai_client.set_provider("anthropic", "claude-3-5-sonnet-latest")
result = ai_client.send("system", "user") result = ai_client.send("system", "user")
assert result == "mocked" assert result == "mocked"
m.assert_called_once() m.assert_called_once()
ai_client.reset_session() ai_client.reset_session()
``` ```
### Integration (real API) ### Integration (real API)
@@ -451,7 +451,7 @@ canonical reference is
### Result-Based Returns ### Result-Based Returns
All `_send_<vendor>_result()` functions (8 vendors: Gemini, Anthropic, All `_send_<vendor>_result()` functions (8 vendors: Gemini, Anthropic,
DeepSeek, MiniMax, Gemini CLI, Qwen, Llama, Grok — plus the DeepSeek, MiniMax, Qwen, Llama, Grok — plus the
`_send_llama_native` Ollama adapter) return `Result[str]` with `errors: list[ErrorInfo]`. SDK `_send_llama_native` Ollama adapter) return `Result[str]` with `errors: list[ErrorInfo]`. SDK
exceptions are caught at the boundary (`src/openai_compatible.py`, exceptions are caught at the boundary (`src/openai_compatible.py`,
`src/qwen_adapter.py`) and converted to `ErrorInfo` dataclasses. The `src/qwen_adapter.py`) and converted to `ErrorInfo` dataclasses. The
@@ -469,10 +469,10 @@ meaning — do not overload `UNKNOWN` when a new failure mode surfaces
### Public API ### Public API
- **`ai_client.send(...)`** — the public API. Returns - **`ai_client.send(...)`** — the public API. Returns
`Result[str]` (with `errors: list[ErrorInfo]` as a side-channel field). `Result[str]` (with `errors: list[ErrorInfo]` as a side-channel field).
Accepts 13+ parameters including 8 callbacks. Accepts 13+ parameters including 8 callbacks.
Internally calls `_send_<vendor>()` for the active provider (the Internally calls `_send_<vendor>()` for the active provider (the
vendor functions return `Result[str]` directly). vendor functions return `Result[str]` directly).
### Example ### Example
@@ -482,9 +482,9 @@ from src.result_types import ErrorKind
r = ai_client.send("system prompt", "user message") r = ai_client.send("system prompt", "user message")
if not r.ok: if not r.ok:
for err in r.errors: for err in r.errors:
log.error(err.ui_message()) log.error(err.ui_message())
# err.kind is one of ErrorKind.*; err.source is "ai_client.<vendor>" # err.kind is one of ErrorKind.*; err.source is "ai_client.<vendor>"
# use r.data regardless (it's the zero-initialized "" on failure) # use r.data regardless (it's the zero-initialized "" on failure)
print(r.data) print(r.data)
``` ```
@@ -492,10 +492,10 @@ print(r.data)
### Migration Notes for Existing Callers ### Migration Notes for Existing Callers
- All production call sites and tests now use `send()`. The - All production call sites and tests now use `send()`. The
legacy `send()` function was removed in the legacy `send()` function was removed in the
`public_api_migration_and_ui_polish_20260615` track. `public_api_migration_and_ui_polish_20260615` track.
- Tests that mock `ai_client._send_<vendor>` should use the - Tests that mock `ai_client._send_<vendor>` should use the
`Result(data=...)` return value pattern. `Result(data=...)` return value pattern.
### See Also (in-doc) ### See Also (in-doc)
@@ -532,34 +532,34 @@ Added 2026-06-06 by the `qwen_llama_grok_integration_20260606` track. Operates o
```python ```python
@dataclass(frozen=True) @dataclass(frozen=True)
class NormalizedResponse: class NormalizedResponse:
text: str text: str
tool_calls: list[dict[str, Any]] tool_calls: list[dict[str, Any]]
usage_input_tokens: int usage_input_tokens: int
usage_output_tokens: int usage_output_tokens: int
usage_cache_read_tokens: int usage_cache_read_tokens: int
usage_cache_creation_tokens: int usage_cache_creation_tokens: int
raw_response: Any raw_response: Any
@dataclass @dataclass
class OpenAICompatibleRequest: class OpenAICompatibleRequest:
messages: list[dict[str, Any]] messages: list[dict[str, Any]]
model: str model: str
temperature: float = 0.0 temperature: float = 0.0
top_p: float = 1.0 top_p: float = 1.0
max_tokens: int = 8192 max_tokens: int = 8192
tools: Optional[list[dict[str, Any]]] = None tools: Optional[list[dict[str, Any]]] = None
tool_choice: str = "auto" tool_choice: str = "auto"
stream: bool = False stream: bool = False
stream_callback: Optional[Callable[[str], None]] = None stream_callback: Optional[Callable[[str], None]] = None
``` ```
### The Function ### The Function
```python ```python
def send_openai_compatible( def send_openai_compatible(
client: Any, # openai.OpenAI client with vendor-specific base_url + auth client: Any, # openai.OpenAI client with vendor-specific base_url + auth
request: OpenAICompatibleRequest, request: OpenAICompatibleRequest,
*, capabilities: "VendorCapabilities", # from src/ai_client.py #region: Vendor Capabilities *, capabilities: "VendorCapabilities", # from src/ai_client.py #region: Vendor Capabilities
) -> NormalizedResponse: ) -> NormalizedResponse:
``` ```
@@ -577,16 +577,16 @@ The function:
```python ```python
# _send_grok, _send_llama (single-shot placeholders), _send_minimax (with restored tool loop) # _send_grok, _send_llama (single-shot placeholders), _send_minimax (with restored tool loop)
def _send_grok(md_content, user_message, base_dir, file_items=None, discussion_history="", stream=False, ...): def _send_grok(md_content, user_message, base_dir, file_items=None, discussion_history="", stream=False, ...):
client = _ensure_grok_client() # openai.OpenAI(api_key=..., base_url="https://api.x.ai/v1") client = _ensure_grok_client() # openai.OpenAI(api_key=..., base_url="https://api.x.ai/v1")
with _grok_history_lock: with _grok_history_lock:
# ... build messages, append user, system + context ... # ... build messages, append user, system + context ...
request = OpenAICompatibleRequest( request = OpenAICompatibleRequest(
messages=messages, model=_model, stream=stream, messages=messages, model=_model, stream=stream,
stream_callback=stream_callback, stream_callback=stream_callback,
) )
caps = get_capabilities("grok", _model) caps = get_capabilities("grok", _model)
response = send_openai_compatible(client, request, capabilities=caps) response = send_openai_compatible(client, request, capabilities=caps)
# ... append to history, return response.text ... # ... append to history, return response.text ...
``` ```
### Qwen Adapter (`src/qwen_adapter.py`) ### Qwen Adapter (`src/qwen_adapter.py`)
@@ -610,28 +610,28 @@ Added 2026-06-11 by the `qwen_llama_grok_followup_20260611` track. Wraps `send_o
```python ```python
def run_with_tool_loop( def run_with_tool_loop(
client: Any, client: Any,
request: OpenAICompatibleRequest | Callable[[int], OpenAICompatibleRequest], request: OpenAICompatibleRequest | Callable[[int], OpenAICompatibleRequest],
*, *,
capabilities: "VendorCapabilities", capabilities: "VendorCapabilities",
pre_tool_callback: Optional[Callable] = None, pre_tool_callback: Optional[Callable] = None,
qa_callback: Optional[Callable] = None, qa_callback: Optional[Callable] = None,
stream_callback: Optional[Callable[[str], None]] = None, stream_callback: Optional[Callable[[str], None]] = None,
patch_callback: Optional[Callable] = None, patch_callback: Optional[Callable] = None,
base_dir: str, base_dir: str,
vendor_name: str, vendor_name: str,
history_lock: Optional[threading.Lock] = None, history_lock: Optional[threading.Lock] = None,
history: Optional[list] = None, history: Optional[list] = None,
trim_func: Optional[Callable] = None, trim_func: Optional[Callable] = None,
send_func: Optional[Callable[[int], "NormalizedResponse"]] = None, send_func: Optional[Callable[[int], "NormalizedResponse"]] = None,
on_pre_dispatch: Optional[Callable] = None, on_pre_dispatch: Optional[Callable] = None,
) -> str: ) -> str:
``` ```
**Two extensions** were added beyond the original signature: **Two extensions** were added beyond the original signature:
1. `request` accepts a `Callable[[int], OpenAICompatibleRequest]` (per-round history rebuild). Use this when the vendor mutates history between rounds (e.g., MiniMax's per-round append). 1. `request` accepts a `Callable[[int], OpenAICompatibleRequest]` (per-round history rebuild). Use this when the vendor mutates history between rounds (e.g., MiniMax's per-round append).
2. `send_func + on_pre_dispatch` allows vendored call paths (e.g., Gemini CLI's `GeminiCliAdapter`) to share the loop + dispatch without going through `send_openai_compatible`. 2. `send_func + on_pre_dispatch` allows vendored call paths (e.g., 's `GeminiCliAdapter`) to share the loop + dispatch without going through `send_openai_compatible`.
**Vendors applied** (as of 2026-06-11): **Vendors applied** (as of 2026-06-11):
- `_send_minimax` (was inline, now uses helper) - `_send_minimax` (was inline, now uses helper)
@@ -657,7 +657,7 @@ Added 2026-06-11. When `_llama_base_url` is `localhost` / `127.0.0.1` (Ollama de
The dispatcher check is in `_send_llama` at the function head: The dispatcher check is in `_send_llama` at the function head:
```python ```python
if "localhost" in _llama_base_url or "127.0.0.1" in _llama_base_url: if "localhost" in _llama_base_url or "127.0.0.1" in _llama_base_url:
return _send_llama_native(...) return _send_llama_native(...)
``` ```
For OpenRouter, custom URLs, and other cloud Llama endpoints, the existing OpenAI-compat path is unchanged. For OpenRouter, custom URLs, and other cloud Llama endpoints, the existing OpenAI-compat path is unchanged.
@@ -714,11 +714,11 @@ The test in `tests/test_aggregate_caching.py` ensures the first N characters of
```python ```python
def test_aggregate_stable_to_volatile_ordering(): def test_aggregate_stable_to_volatile_ordering():
ctrl = mock_app_controller() ctrl = mock_app_controller()
turn1 = aggregate.build_initial_context(ctrl, user_message="first") turn1 = aggregate.build_initial_context(ctrl, user_message="first")
turn2 = aggregate.build_initial_context(ctrl, user_message="second") turn2 = aggregate.build_initial_context(ctrl, user_message="second")
N = aggregate.stable_prefix_length(ctrl) N = aggregate.stable_prefix_length(ctrl)
assert turn1[:N] == turn2[:N], f"Stable prefix mismatch: {turn1[:N]!r} != {turn2[:N]!r}" assert turn1[:N] == turn2[:N], f"Stable prefix mismatch: {turn1[:N]!r} != {turn2[:N]!r}"
``` ```
**The test is the contract.** If a new layer is added in the wrong position, the test fails; the agent must move the layer to the stable position or update the test with written justification. **The test is the contract.** If a new layer is added in the wrong position, the test fails; the agent must move the layer to the stable position or update the test with written justification.
@@ -729,17 +729,17 @@ def test_aggregate_stable_to_volatile_ordering():
```python ```python
def _send_anthropic(messages, *, cache_prefix_chars=None): def _send_anthropic(messages, *, cache_prefix_chars=None):
if cache_prefix_chars is not None: if cache_prefix_chars is not None:
content_blocks = cache_prefix_blocks(messages, cache_prefix_chars) content_blocks = cache_prefix_blocks(messages, cache_prefix_chars)
else: else:
content_blocks = messages content_blocks = messages
response = anthropic_client.messages.create( response = anthropic_client.messages.create(
model=model, model=model,
max_tokens=8192, max_tokens=8192,
messages=[{"role": "user", "content": content_blocks}], messages=[{"role": "user", "content": content_blocks}],
) )
return _result_with_usage(response.content, response.usage, messages) return _result_with_usage(response.content, response.usage, messages)
``` ```
**The `cache_prefix_blocks` helper** splits the message at the given char offsets and marks each prefix with `cache_control: {"type": "ephemeral"}`. Max 3 prefix blocks (provider limit is 4 breakpoints per request). **The `cache_prefix_blocks` helper** splits the message at the given char offsets and marks each prefix with `cache_control: {"type": "ephemeral"}`. Max 3 prefix blocks (provider limit is 4 breakpoints per request).
@@ -750,17 +750,17 @@ def _send_anthropic(messages, *, cache_prefix_chars=None):
```python ```python
def _send_gemini(messages, *, cache_ttl_seconds=3600): def _send_gemini(messages, *, cache_ttl_seconds=3600):
if cache_ttl_seconds > 0: if cache_ttl_seconds > 0:
cached_content = genai_client.caches.create( cached_content = genai_client.caches.create(
model=model, contents=stable_prefix_messages, ttl=f"{cache_ttl_seconds}s", model=model, contents=stable_prefix_messages, ttl=f"{cache_ttl_seconds}s",
) )
response = genai_client.models.generate_content( response = genai_client.models.generate_content(
model=model, contents=volatile_messages, model=model, contents=volatile_messages,
config=genai.types.GenerateContentConfig(cached_content=cached_content.name), config=genai.types.GenerateContentConfig(cached_content=cached_content.name),
) )
else: else:
response = genai_client.models.generate_content(model=model, contents=messages) response = genai_client.models.generate_content(model=model, contents=messages)
return _result_with_usage(response.text, response.usage_metadata, messages) return _result_with_usage(response.text, response.usage_metadata, messages)
``` ```
**The default TTL is 1 hour**; configurable per-discussion via the GUI. **The default TTL is 1 hour**; configurable per-discussion via the GUI.
@@ -783,21 +783,21 @@ No application-side control; the provider handles caching. The GUI just shows "C
```python ```python
@dataclass @dataclass
class DiscussionCacheState: class DiscussionCacheState:
discussion_id: str discussion_id: str
provider: str provider: str
cached_at: datetime cached_at: datetime
expires_at: Optional[datetime] # None for OpenAI implicit expires_at: Optional[datetime] # None for OpenAI implicit
hit_count: int = 0 hit_count: int = 0
tokens_cached: int = 0 tokens_cached: int = 0
last_invalidated_at: Optional[datetime] = None last_invalidated_at: Optional[datetime] = None
caching_enabled: bool = True caching_enabled: bool = True
``` ```
**The Hook API additions:** **The Hook API additions:**
``` ```
GET /api/cache # list all discussion cache states GET /api/cache # list all discussion cache states
GET /api/cache/<discussion_id> # get one GET /api/cache/<discussion_id> # get one
POST /api/cache/<discussion_id>/invalidate POST /api/cache/<discussion_id>/invalidate
POST /api/cache/<discussion_id>/disable POST /api/cache/<discussion_id>/disable
POST /api/cache/<discussion_id>/enable POST /api/cache/<discussion_id>/enable
@@ -809,15 +809,15 @@ POST /api/cache/<discussion_id>/enable
```python ```python
def _send_claude_code(message, model, *, allowed_tools=None, max_turns=1): def _send_claude_code(message, model, *, allowed_tools=None, max_turns=1):
options = ClaudeAgentOptions( options = ClaudeAgentOptions(
model=None if not model or model == "default" else model, model=None if not model or model == "default" else model,
max_turns=max_turns, max_turns=max_turns,
tools=list(allowed_tools) if allowed_tools else [], tools=list(allowed_tools) if allowed_tools else [],
allowed_tools=list(allowed_tools) if allowed_tools else [], allowed_tools=list(allowed_tools) if allowed_tools else [],
cwd=os.getcwd(), cwd=os.getcwd(),
) )
# ... claude_agent_sdk.query(prompt=message, options=options) # ... claude_agent_sdk.query(prompt=message, options=options)
return _result_with_usage(text, usage, message) return _result_with_usage(text, usage, message)
``` ```
### The cross-references ### The cross-references
+285 -285
View File
@@ -20,20 +20,20 @@ The codebase is organized into a `src/` layout to separate implementation from c
``` ```
manual_slop/ manual_slop/
├── conductor/ # Conductor tracks, specs, and plans ├── conductor/ # Conductor tracks, specs, and plans
├── docs/ # Deep-dive architectural documentation ├── docs/ # Deep-dive architectural documentation
├── logs/ # Session logs, agent traces, and errors ├── logs/ # Session logs, agent traces, and errors
├── scripts/ # Build, migration, and IPC bridge scripts ├── scripts/ # Build, migration, and IPC bridge scripts
├── src/ # Core Python implementation ├── src/ # Core Python implementation
├── ai_client.py # LLM provider abstraction │ ├── ai_client.py # LLM provider abstraction
├── gui_2.py # Main ImGui application │ ├── gui_2.py # Main ImGui application
├── mcp_client.py # MCP tool implementation │ ├── mcp_client.py # MCP tool implementation
└── ... # Other core modules │ └── ... # Other core modules
├── tests/ # Pytest suite and simulation fixtures ├── tests/ # Pytest suite and simulation fixtures
├── simulation/ # Workflow and agent simulation logic ├── simulation/ # Workflow and agent simulation logic
├── sloppy.py # Primary application entry point ├── sloppy.py # Primary application entry point
├── config.toml # Global application settings ├── config.toml # Global application settings
└── manual_slop.toml # Project-specific configuration └── manual_slop.toml # Project-specific configuration
``` ```
--- ---
@@ -59,9 +59,9 @@ self._loop_thread.start()
# _run_event_loop: # _run_event_loop:
def _run_event_loop(self) -> None: def _run_event_loop(self) -> None:
asyncio.set_event_loop(self._loop) asyncio.set_event_loop(self._loop)
self._loop.create_task(self._process_event_queue()) self._loop.create_task(self._process_event_queue())
self._loop.run_forever() self._loop.run_forever()
``` ```
The GUI thread uses `asyncio.run_coroutine_threadsafe(coro, self._loop)` to push work into this loop. The GUI thread uses `asyncio.run_coroutine_threadsafe(coro, self._loop)` to push work into this loop.
@@ -75,12 +75,12 @@ For concurrent multi-agent execution, the application uses `threading.local()` t
_local_storage = threading.local() _local_storage = threading.local()
def get_current_tier() -> Optional[str]: def get_current_tier() -> Optional[str]:
"""Returns the current tier from thread-local storage.""" """Returns the current tier from thread-local storage."""
return getattr(_local_storage, "current_tier", None) return getattr(_local_storage, "current_tier", None)
def set_current_tier(tier: Optional[str]) -> None: def set_current_tier(tier: Optional[str]) -> None:
"""Sets the current tier in thread-local storage.""" """Sets the current tier in thread-local storage."""
_local_storage.current_tier = tier _local_storage.current_tier = tier
``` ```
This ensures that comms log entries and tool calls are correctly tagged with their source tier even when multiple workers execute concurrently. This ensures that comms log entries and tool calls are correctly tagged with their source tier even when multiple workers execute concurrently.
@@ -96,10 +96,10 @@ All cross-thread communication uses one of three patterns:
```python ```python
# events.py # events.py
class AsyncEventQueue: class AsyncEventQueue:
_queue: asyncio.Queue # holds Tuple[str, Any] items _queue: asyncio.Queue # holds Tuple[str, Any] items
async def put(self, event_name: str, payload: Any = None) -> None async def put(self, event_name: str, payload: Any = None) -> None
async def get(self) -> Tuple[str, Any] async def get(self) -> Tuple[str, Any]
``` ```
The central event bus. Uses `asyncio.Queue`, so non-asyncio threads must enqueue via `asyncio.run_coroutine_threadsafe()`. Consumer is `App._process_event_queue()`, running as a long-lived coroutine on the asyncio loop. The central event bus. Uses `asyncio.Queue`, so non-asyncio threads must enqueue via `asyncio.run_coroutine_threadsafe()`. Consumer is `App._process_event_queue()`, running as a long-lived coroutine on the asyncio loop.
@@ -125,8 +125,8 @@ self._pending_history_adds_lock = threading.Lock()
Additional locks: Additional locks:
```python ```python
self._send_thread_lock = threading.Lock() # Guards send_thread creation self._send_thread_lock = threading.Lock() # Guards send_thread creation
self._pending_dialog_lock = threading.Lock() # Guards _pending_dialog + _pending_actions dict self._pending_dialog_lock = threading.Lock() # Guards _pending_dialog + _pending_actions dict
``` ```
### Pattern C: Condition-Variable Dialogs (Bidirectional Blocking) ### Pattern C: Condition-Variable Dialogs (Bidirectional Blocking)
@@ -143,10 +143,10 @@ Three classes in `events.py` (89 lines, no external dependencies beyond `asyncio
```python ```python
class EventEmitter: class EventEmitter:
_listeners: Dict[str, List[Callable]] _listeners: Dict[str, List[Callable]]
def on(self, event_name: str, callback: Callable) -> None def on(self, event_name: str, callback: Callable) -> None
def emit(self, event_name: str, *args: Any, **kwargs: Any) -> None def emit(self, event_name: str, *args: Any, **kwargs: Any) -> None
``` ```
Synchronous pub-sub. Callbacks execute in the caller's thread. Used by `ai_client.events` for lifecycle hooks (`request_start`, `response_received`, `tool_execution`). No thread safety — relies on consistent single-thread usage. Synchronous pub-sub. Callbacks execute in the caller's thread. Used by `ai_client.events` for lifecycle hooks (`request_start`, `response_received`, `tool_execution`). No thread safety — relies on consistent single-thread usage.
@@ -159,13 +159,13 @@ Described above in Pattern A.
```python ```python
class UserRequestEvent: class UserRequestEvent:
prompt: str # User's raw input text prompt: str # User's raw input text
stable_md: str # Generated markdown context (files, screenshots) stable_md: str # Generated markdown context (files, screenshots)
file_items: List[Any] # File attachment items for dynamic refresh file_items: List[Any] # File attachment items for dynamic refresh
disc_text: str # Serialized discussion history disc_text: str # Serialized discussion history
base_dir: str # Working directory for shell commands base_dir: str # Working directory for shell commands
def to_dict(self) -> Dict[str, Any] def to_dict(self) -> Dict[str, Any]
``` ```
Pure data carrier. Created on the GUI thread in `_handle_generate_send`, consumed on the asyncio thread in `_handle_request_event`. Pure data carrier. Created on the GUI thread in `_handle_generate_send`, consumed on the asyncio thread in `_handle_request_event`.
@@ -180,8 +180,8 @@ The `App.__init__` (lines 152-296) follows this precise order:
1. **Config hydration**: Reads `config.toml` (global) and `<project>.toml` (local). Builds the initial "world view" — tracked files, discussion history, active models. 1. **Config hydration**: Reads `config.toml` (global) and `<project>.toml` (local). Builds the initial "world view" — tracked files, discussion history, active models.
2. **Thread bootstrapping**: 2. **Thread bootstrapping**:
- Asyncio event loop thread starts (`_loop_thread`). - Asyncio event loop thread starts (`_loop_thread`).
- `HookServer` starts as a daemon if `test_hooks_enabled` or provider is `gemini_cli`. - `HookServer` starts as a daemon if `test_hooks_enabled` or provider is `gemini_cli`.
3. **Callback wiring** (`_init_ai_and_hooks`): Connects `ai_client.confirm_and_run_callback`, `comms_log_callback`, `tool_log_callback` to GUI handlers. 3. **Callback wiring** (`_init_ai_and_hooks`): Connects `ai_client.confirm_and_run_callback`, `comms_log_callback`, `tool_log_callback` to GUI handlers.
4. **UI entry**: Main thread enters `immapp.run()`. GUI is now alive; background threads are ready. 4. **UI entry**: Main thread enters `immapp.run()`. GUI is now alive; background threads are ready.
@@ -199,10 +199,10 @@ The asyncio loop thread is a daemon — it dies with the process. `App.shutdown(
```python ```python
def shutdown(self) -> None: def shutdown(self) -> None:
if self._loop.is_running(): if self._loop.is_running():
self._loop.call_soon_threadsafe(self._loop.stop) self._loop.call_soon_threadsafe(self._loop.stop)
if self._loop_thread.is_alive(): if self._loop_thread.is_alive():
self._loop_thread.join(timeout=2.0) self._loop_thread.join(timeout=2.0)
``` ```
--- ---
@@ -212,25 +212,25 @@ def shutdown(self) -> None:
### Request Flow ### Request Flow
``` ```
GUI Thread Asyncio Thread GUI Thread (next frame) GUI Thread Asyncio Thread GUI Thread (next frame)
────────── ────────────── ────────────────────── ────────── ────────────── ──────────────────────
1. User clicks "Gen + Send" 1. User clicks "Gen + Send"
2. _handle_generate_send(): 2. _handle_generate_send():
- Compiles md context - Compiles md context
- Creates UserRequestEvent - Creates UserRequestEvent
- Enqueues via - Enqueues via
run_coroutine_threadsafe ──> 3. _process_event_queue(): run_coroutine_threadsafe ──> 3. _process_event_queue():
awaits event_queue.get() awaits event_queue.get()
routes "user_request" to routes "user_request" to
_handle_request_event() _handle_request_event()
4. Configures ai_client 4. Configures ai_client
5. ai_client.send() BLOCKS 5. ai_client.send() BLOCKS
(seconds to minutes) (seconds to minutes)
6. On completion, enqueues 6. On completion, enqueues
"response" event back ──> 7. _process_pending_gui_tasks(): "response" event back ──> 7. _process_pending_gui_tasks():
Drains task list under lock Drains task list under lock
Sets ai_response text Sets ai_response text
Triggers terminal blink Triggers terminal blink
``` ```
### Event Types Routed by `_process_event_queue` ### Event Types Routed by `_process_event_queue`
@@ -253,13 +253,13 @@ Called once per ImGui frame on the **main GUI thread**. This is the sole safe po
```python ```python
def _process_pending_gui_tasks(self) -> None: def _process_pending_gui_tasks(self) -> None:
if not self._pending_gui_tasks: if not self._pending_gui_tasks:
return return
with self._pending_gui_tasks_lock: with self._pending_gui_tasks_lock:
tasks = self._pending_gui_tasks[:] # Snapshot tasks = self._pending_gui_tasks[:] # Snapshot
self._pending_gui_tasks.clear() # Release lock fast self._pending_gui_tasks.clear() # Release lock fast
for task in tasks: for task in tasks:
# Process each task outside the lock # Process each task outside the lock
``` ```
Acquires the lock briefly to snapshot the task list, then processes outside the lock. Minimizes lock contention with producer threads. Acquires the lock briefly to snapshot the task list, then processes outside the lock. Minimizes lock contention with producer threads.
@@ -294,43 +294,43 @@ The "Execution Clutch" ensures every destructive AI action passes through an aud
```python ```python
class ConfirmDialog: class ConfirmDialog:
_uid: str # uuid4 identifier _uid: str # uuid4 identifier
_script: str # The PowerShell script text (editable) _script: str # The PowerShell script text (editable)
_base_dir: str # Working directory _base_dir: str # Working directory
_condition: threading.Condition # Blocking primitive _condition: threading.Condition # Blocking primitive
_done: bool # Signal flag _done: bool # Signal flag
_approved: bool # User's decision _approved: bool # User's decision
def wait(self) -> tuple[bool, str] # Blocks until _done; returns (approved, script) def wait(self) -> tuple[bool, str] # Blocks until _done; returns (approved, script)
``` ```
**`MMAApprovalDialog`** — MMA tier step approval: **`MMAApprovalDialog`** — MMA tier step approval:
```python ```python
class MMAApprovalDialog: class MMAApprovalDialog:
_ticket_id: str _ticket_id: str
_payload: str # The step payload (editable) _payload: str # The step payload (editable)
_condition: threading.Condition _condition: threading.Condition
_done: bool _done: bool
_approved: bool _approved: bool
def wait(self) -> tuple[bool, str] # Returns (approved, payload) def wait(self) -> tuple[bool, str] # Returns (approved, payload)
``` ```
**`MMASpawnApprovalDialog`** — Sub-agent spawn approval: **`MMASpawnApprovalDialog`** — Sub-agent spawn approval:
```python ```python
class MMASpawnApprovalDialog: class MMASpawnApprovalDialog:
_ticket_id: str _ticket_id: str
_role: str # tier3-worker, tier4-qa, etc. _role: str # tier3-worker, tier4-qa, etc.
_prompt: str # Spawn prompt (editable) _prompt: str # Spawn prompt (editable)
_context_md: str # Context document (editable) _context_md: str # Context document (editable)
_condition: threading.Condition _condition: threading.Condition
_done: bool _done: bool
_approved: bool _approved: bool
_abort: bool # Can abort entire track _abort: bool # Can abort entire track
def wait(self) -> dict[str, Any] # Returns {approved, abort, prompt, context_md} def wait(self) -> dict[str, Any] # Returns {approved, abort, prompt, context_md}
``` ```
### Blocking Flow ### Blocking Flow
@@ -338,27 +338,27 @@ class MMASpawnApprovalDialog:
Using `ConfirmDialog` as exemplar: Using `ConfirmDialog` as exemplar:
``` ```
ASYNCIO THREAD (ai_client tool callback) GUI MAIN THREAD ASYNCIO THREAD (ai_client tool callback) GUI MAIN THREAD
───────────────────────────────────────── ─────────────── ───────────────────────────────────────── ───────────────
1. ai_client calls _confirm_and_run(script) 1. ai_client calls _confirm_and_run(script)
2. Creates ConfirmDialog(script, base_dir) 2. Creates ConfirmDialog(script, base_dir)
3. Stores dialog: 3. Stores dialog:
- Headless: _pending_actions[uid] = dialog - Headless: _pending_actions[uid] = dialog
- GUI mode: _pending_dialog = dialog - GUI mode: _pending_dialog = dialog
4. If test_hooks_enabled: 4. If test_hooks_enabled:
pushes to _api_event_queue pushes to _api_event_queue
5. dialog.wait() BLOCKS on _condition 5. dialog.wait() BLOCKS on _condition
6. Next frame: ImGui renders 6. Next frame: ImGui renders
_pending_dialog in modal _pending_dialog in modal
7. User clicks Approve/Reject 7. User clicks Approve/Reject
8. _handle_approve_script(): 8. _handle_approve_script():
with dialog._condition: with dialog._condition:
dialog._approved = True dialog._approved = True
dialog._done = True dialog._done = True
dialog._condition.notify_all() dialog._condition.notify_all()
9. wait() returns (True, potentially_edited_script) 9. wait() returns (True, potentially_edited_script)
10. Executes shell_runner.run_powershell() 10. Executes shell_runner.run_powershell()
11. Returns output to ai_client 11. Returns output to ai_client
``` ```
The `_condition.wait(timeout=0.1)` uses a 100ms polling interval inside a loop — a polling-with-condition hybrid that ensures the blocking thread wakes periodically. The `_condition.wait(timeout=0.1)` uses a 100ms polling interval inside a loop — a polling-with-condition hybrid that ensures the blocking thread wakes periodically.
@@ -373,14 +373,14 @@ The `_condition.wait(timeout=0.1)` uses a 100ms polling interval inside a loop
```python ```python
def resolve_pending_action(self, action_id: str, approved: bool) -> bool: def resolve_pending_action(self, action_id: str, approved: bool) -> bool:
with self._pending_dialog_lock: with self._pending_dialog_lock:
if action_id in self._pending_actions: if action_id in self._pending_actions:
dialog = self._pending_actions[action_id] dialog = self._pending_actions[action_id]
with dialog._condition: with dialog._condition:
dialog._approved = approved dialog._approved = approved
dialog._done = True dialog._done = True
dialog._condition.notify_all() dialog._condition.notify_all()
return True return True
``` ```
**MMA approval path**: **MMA approval path**:
@@ -395,14 +395,14 @@ def resolve_pending_action(self, action_id: str, approved: bool) -> bool:
### Module-Level State ### Module-Level State
```python ```python
_provider: str = "gemini" # "gemini" | "anthropic" | "deepseek" | "gemini_cli" | "minimax" _provider: str = "gemini" # "gemini" | "anthropic" | "deepseek" | "gemini_cli" | "minimax"
_model: str = "gemini-2.5-flash-lite" _model: str = "gemini-2.5-flash-lite"
_temperature: float = 0.0 _temperature: float = 0.0
_top_p: float = 1.0 _top_p: float = 1.0
_max_tokens: int = 8192 _max_tokens: int = 8192
_history_trunc_limit: int = 8000 # Char limit for truncating old tool outputs _history_trunc_limit: int = 8000 # Char limit for truncating old tool outputs
_send_lock: threading.Lock # Serializes ALL send() calls across providers _send_lock: threading.Lock # Serializes ALL send() calls across providers
``` ```
Per-provider client objects: Per-provider client objects:
@@ -410,16 +410,16 @@ Per-provider client objects:
```python ```python
# Gemini (SDK-managed stateful chat) # Gemini (SDK-managed stateful chat)
_gemini_client: genai.Client | None _gemini_client: genai.Client | None
_gemini_chat: Any # Holds history internally _gemini_chat: Any # Holds history internally
_gemini_cache: Any # Server-side CachedContent _gemini_cache: Any # Server-side CachedContent
_gemini_cache_md_hash: str | None # Hash for cache invalidation _gemini_cache_md_hash: str | None # Hash for cache invalidation
_gemini_cache_created_at: float | None # Monotonic time of cache creation _gemini_cache_created_at: float | None # Monotonic time of cache creation
_gemini_cached_file_paths: list[str] # File paths included in the active cache _gemini_cached_file_paths: list[str] # File paths included in the active cache
_GEMINI_CACHE_TTL: int = 3600 # 1-hour; rebuilt at 90% (3240s) _GEMINI_CACHE_TTL: int = 3600 # 1-hour; rebuilt at 90% (3240s)
# Anthropic (client-managed history) # Anthropic (client-managed history)
_anthropic_client: anthropic.Anthropic | None _anthropic_client: anthropic.Anthropic | None
_anthropic_history: list[dict] # Mutable [{role, content}, ...] _anthropic_history: list[dict] # Mutable [{role, content}, ...]
_anthropic_history_lock: threading.Lock _anthropic_history_lock: threading.Lock
# DeepSeek (raw HTTP, client-managed history) # DeepSeek (raw HTTP, client-managed history)
@@ -439,27 +439,27 @@ _gemini_cli_adapter: GeminiCliAdapter | None
Safety limits: Safety limits:
```python ```python
MAX_TOOL_ROUNDS: int = 10 # Max tool-call loop iterations per send() MAX_TOOL_ROUNDS: int = 10 # Max tool-call loop iterations per send()
_MAX_TOOL_OUTPUT_BYTES: int = 500_000 # 500KB cumulative tool output budget _MAX_TOOL_OUTPUT_BYTES: int = 500_000 # 500KB cumulative tool output budget
_ANTHROPIC_CHUNK_SIZE: int = 120_000 # Max chars per system text block _ANTHROPIC_CHUNK_SIZE: int = 120_000 # Max chars per system text block
_ANTHROPIC_MAX_PROMPT_TOKENS: int = 180_000 # 200k limit minus headroom _ANTHROPIC_MAX_PROMPT_TOKENS: int = 180_000 # 200k limit minus headroom
_GEMINI_MAX_INPUT_TOKENS: int = 900_000 # 1M window minus headroom _GEMINI_MAX_INPUT_TOKENS: int = 900_000 # 1M window minus headroom
``` ```
### The `send()` Dispatcher ### The `send()` Dispatcher
```python ```python
def send(md_content, user_message, base_dir=".", file_items=None, def send(md_content, user_message, base_dir=".", file_items=None,
discussion_history="", stream=False, discussion_history="", stream=False,
pre_tool_callback=None, qa_callback=None, pre_tool_callback=None, qa_callback=None,
enable_tools=True, stream_callback=None, patch_callback=None, enable_tools=True, stream_callback=None, patch_callback=None,
rag_engine=None) -> str: rag_engine=None) -> str:
with _send_lock: with _send_lock:
if _provider == "gemini": return _send_gemini(...) if _provider == "gemini": return _send_gemini(...)
elif _provider == "gemini_cli": return _send_gemini_cli(...) elif _provider == "gemini_cli": return _send_gemini_cli(...)
elif _provider == "anthropic": return _send_anthropic(...) elif _provider == "anthropic": return _send_anthropic(...)
elif _provider == "deepseek": return _send_deepseek(..., stream=stream) elif _provider == "deepseek": return _send_deepseek(..., stream=stream)
elif _provider == "minimax": return _send_minimax(..., stream=stream) elif _provider == "minimax": return _send_minimax(..., stream=stream)
``` ```
`_send_lock` serializes all API calls — only one provider call can be in-flight at a time. All providers share the same callback signatures. Return type is always `str`. `_send_lock` serializes all API calls — only one provider call can be in-flight at a time. All providers share the same callback signatures. Return type is always `str`.
@@ -496,11 +496,11 @@ All providers follow the same high-level loop, iterated up to `MAX_TOOL_ROUNDS +
3. Log to comms log; emit events. 3. Log to comms log; emit events.
4. If no function calls or max rounds exceeded: **break**. 4. If no function calls or max rounds exceeded: **break**.
5. For each function call: 5. For each function call:
- If `pre_tool_callback` rejects: return rejection text. - If `pre_tool_callback` rejects: return rejection text.
- Dispatch to `mcp_client.dispatch()` or `shell_runner.run_powershell()`. - Dispatch to `mcp_client.dispatch()` or `shell_runner.run_powershell()`.
- After the **last** call of this round: run `_reread_file_items()` for context refresh. - After the **last** call of this round: run `_reread_file_items()` for context refresh.
- Truncate tool output at `_history_trunc_limit` chars. - Truncate tool output at `_history_trunc_limit` chars.
- Accumulate `_cumulative_tool_bytes`. - Accumulate `_cumulative_tool_bytes`.
6. If cumulative bytes > 500KB: inject warning. 6. If cumulative bytes > 500KB: inject warning.
7. Package tool results in provider-specific format; loop. 7. Package tool results in provider-specific format; loop.
@@ -512,8 +512,8 @@ After the last tool call in each round, `_reread_file_items(file_items)` checks
2. If unchanged: pass through as-is. 2. If unchanged: pass through as-is.
3. If changed: re-read content, store `old_content` for diffing, update `mtime`. 3. If changed: re-read content, store `old_content` for diffing, update `mtime`.
4. Changed files are diffed via `_build_file_diff_text`: 4. Changed files are diffed via `_build_file_diff_text`:
- Files <= 200 lines: emit full content. - Files <= 200 lines: emit full content.
- Files > 200 lines with `old_content`: emit `difflib.unified_diff`. - Files > 200 lines with `old_content`: emit `difflib.unified_diff`.
5. Diff is appended to the last tool's output as `[SYSTEM: FILES UPDATED]\n\n{diff}`. 5. Diff is appended to the last tool's output as `[SYSTEM: FILES UPDATED]\n\n{diff}`.
6. Stale `[FILES UPDATED]` blocks are stripped from older history turns by `_strip_stale_file_refreshes` to prevent context bloat. 6. Stale `[FILES UPDATED]` blocks are stripped from older history turns by `_strip_stale_file_refreshes` to prevent context bloat.
@@ -550,27 +550,27 @@ Independent tool calls within a single round execute concurrently via `asyncio.g
```python ```python
async def _execute_tool_calls_concurrently( async def _execute_tool_calls_concurrently(
calls: list[Any], calls: list[Any],
base_dir: str, base_dir: str,
pre_tool_callback: ..., pre_tool_callback: ...,
qa_callback: ..., qa_callback: ...,
r_idx: int, r_idx: int,
provider: str, provider: str,
patch_callback: ... = None, patch_callback: ... = None,
) -> list[tuple[str, str, str, str]]: # (tool_name, call_id, output, original_name) ) -> list[tuple[str, str, str, str]]: # (tool_name, call_id, output, original_name)
... ...
``` ```
### Per-Call Worker ### Per-Call Worker
```python ```python
async def _execute_single_tool_call_async( async def _execute_single_tool_call_async(
name: str, args: dict, call_id: str, base_dir: str, name: str, args: dict, call_id: str, base_dir: str,
pre_tool_callback, qa_callback, r_idx: int, pre_tool_callback, qa_callback, r_idx: int,
tier: str | None = None, tier: str | None = None,
patch_callback = None, patch_callback = None,
) -> tuple[str, str, str, str]: ) -> tuple[str, str, str, str]:
... ...
``` ```
`tier: str | None` is propagated to the comms log and pre-tool callback so audit trails can attribute tool calls to a specific MMA tier (e.g., "Tier 3", "Tier 4"). Thread-local `_local_storage.current_tier` is the source; the parameter is the explicit pass-through. `tier: str | None` is propagated to the comms log and pre-tool callback so audit trails can attribute tool calls to a specific MMA tier (e.g., "Tier 3", "Tier 4"). Thread-local `_local_storage.current_tier` is the source; the parameter is the explicit pass-through.
@@ -587,11 +587,11 @@ If any individual call raises, `asyncio.gather` with `return_exceptions=True` co
```python ```python
def send(md_content, user_message, base_dir=".", file_items=None, ..., def send(md_content, user_message, base_dir=".", file_items=None, ...,
rag_engine: Optional[Any] = None) -> str: rag_engine: Optional[Any] = None) -> str:
if rag_engine is not None: if rag_engine is not None:
retrieved = rag_engine.query(user_message, top_k=5) retrieved = rag_engine.query(user_message, top_k=5)
md_content = _inject_rag_context(md_content, retrieved) md_content = _inject_rag_context(md_content, retrieved)
... ...
``` ```
The RAG engine is **not** owned by `ai_client`; the caller (typically `AppController` for the main discussion flow, or `multi_agent_conductor.run_worker_lifecycle` for Tier 3 workers) is responsible for instantiating and configuring it. This keeps `ai_client` decoupled from any specific retrieval backend (ChromaDB local, external MCP RAG server, or none). The RAG engine is **not** owned by `ai_client`; the caller (typically `AppController` for the main discussion flow, or `multi_agent_conductor.run_worker_lifecycle` for Tier 3 workers) is responsible for instantiating and configuring it. This keeps `ai_client` decoupled from any specific retrieval backend (ChromaDB local, external MCP RAG server, or none).
@@ -612,7 +612,7 @@ When a Tier 3 worker's test run fails, the engine can request a Tier 4 patch ins
```python ```python
def run_tier4_patch_generation(error: str, file_context: str) -> str: def run_tier4_patch_generation(error: str, file_context: str) -> str:
... ...
``` ```
### Flow ### Flow
@@ -637,7 +637,7 @@ Long discussions accumulate tool outputs and intermediate reasoning that bloat t
```python ```python
def run_discussion_compression(discussion_text: str) -> str: def run_discussion_compression(discussion_text: str) -> str:
... ...
``` ```
### Flow ### Flow
@@ -649,7 +649,7 @@ def run_discussion_compression(discussion_text: str) -> str:
### Provider Robustness ### Provider Robustness
The function tolerates case- and whitespace-variation in the provider string (`" MiniMax "` is normalized to `"minimax"`). This is important because the active provider may be set via different code paths (TOML, env var, runtime override). The function tolerates case- and whitespace-variation in the provider string (`" MiniMax "` is normalized to `"minimax"`). This is important because the active provider may be set via different code paths (TOML, env var, runtime override).
--- ---
@@ -661,7 +661,7 @@ For very large files, the heuristic `summarise_file` in `src/summarize.py` may b
```python ```python
def run_subagent_summarization(file_path: str, content: str, is_code: bool, outline: str) -> str: def run_subagent_summarization(file_path: str, content: str, is_code: bool, outline: str) -> str:
... ...
``` ```
### When Invoked ### When Invoked
@@ -688,17 +688,17 @@ Every API interaction is logged to a module-level list with real-time GUI push:
```python ```python
def _append_comms(direction: str, kind: str, payload: dict[str, Any]) -> None: def _append_comms(direction: str, kind: str, payload: dict[str, Any]) -> None:
entry = { entry = {
"ts": datetime.now().strftime("%H:%M:%S"), "ts": datetime.now().strftime("%H:%M:%S"),
"direction": direction, # "OUT" (to API) or "IN" (from API) "direction": direction, # "OUT" (to API) or "IN" (from API)
"kind": kind, # "request" | "response" | "tool_call" | "tool_result" "kind": kind, # "request" | "response" | "tool_call" | "tool_result"
"provider": _provider, "provider": _provider,
"model": _model, "model": _model,
"payload": payload, "payload": payload,
} }
_comms_log.append(entry) _comms_log.append(entry)
if comms_log_callback: if comms_log_callback:
comms_log_callback(entry) # Real-time push to GUI comms_log_callback(entry) # Real-time push to GUI
``` ```
--- ---
@@ -709,10 +709,10 @@ def _append_comms(direction: str, kind: str, payload: dict[str, Any]) -> None:
``` ```
"idle" -> "sending..." -> [AI call in progress] "idle" -> "sending..." -> [AI call in progress]
-> "running powershell..." -> "powershell done, awaiting AI..." -> "running powershell..." -> "powershell done, awaiting AI..."
-> "fetching url..." | "searching web..." -> "fetching url..." | "searching web..."
-> "done" | "error" -> "done" | "error"
-> "idle" (on reset) -> "idle" (on reset)
``` ```
### HITL Dialog State (Binary per type) ### HITL Dialog State (Binary per type)
@@ -748,32 +748,32 @@ Every interaction is designed to be auditable:
```python ```python
# Comms log entry (JSON-L) # Comms log entry (JSON-L)
{ {
"ts": "14:32:05", "ts": "14:32:05",
"direction": "OUT", "direction": "OUT",
"kind": "tool_call", "kind": "tool_call",
"provider": "gemini", "provider": "gemini",
"model": "gemini-2.5-flash-lite", "model": "gemini-2.5-flash-lite",
"payload": { "payload": {
"name": "run_powershell", "name": "run_powershell",
"id": "call_abc123", "id": "call_abc123",
"script": "Get-ChildItem" "script": "Get-ChildItem"
}, },
"source_tier": "Tier 3", "source_tier": "Tier 3",
"local_ts": 1709875925.123 "local_ts": 1709875925.123
} }
# Performance metrics (via get_metrics()) # Performance metrics (via get_metrics())
{ {
"fps": 60.0, "fps": 60.0,
"fps_avg": 58.5, "fps_avg": 58.5,
"last_frame_time_ms": 16.67, "last_frame_time_ms": 16.67,
"frame_time_ms_avg": 17.1, "frame_time_ms_avg": 17.1,
"cpu_percent": 12.5, "cpu_percent": 12.5,
"cpu_percent_avg": 15.2, "cpu_percent_avg": 15.2,
"input_lag_ms": 2.3, "input_lag_ms": 2.3,
"input_lag_ms_avg": 3.1, "input_lag_ms_avg": 3.1,
"time_render_mma_dashboard_ms": 5.2, "time_render_mma_dashboard_ms": 5.2,
"time_render_mma_dashboard_ms_avg": 4.8 "time_render_mma_dashboard_ms_avg": 4.8
} }
``` ```
@@ -787,30 +787,30 @@ The `WorkerPool` class in `multi_agent_conductor.py` manages a bounded pool of w
```python ```python
class WorkerPool: class WorkerPool:
def __init__(self, max_workers: int = 4): def __init__(self, max_workers: int = 4):
self.max_workers = max_workers self.max_workers = max_workers
self._active: dict[str, threading.Thread] = {} self._active: dict[str, threading.Thread] = {}
self._lock = threading.Lock() self._lock = threading.Lock()
self._semaphore = threading.Semaphore(max_workers) self._semaphore = threading.Semaphore(max_workers)
def spawn(self, ticket_id: str, target: Callable, args: tuple) -> Optional[threading.Thread]: def spawn(self, ticket_id: str, target: Callable, args: tuple) -> Optional[threading.Thread]:
with self._lock: with self._lock:
if len(self._active) >= self.max_workers: if len(self._active) >= self.max_workers:
return None return None
def wrapper(*a, **kw): def wrapper(*a, **kw):
try: try:
with self._semaphore: with self._semaphore:
target(*a, **kw) target(*a, **kw)
finally: finally:
with self._lock: with self._lock:
self._active.pop(ticket_id, None) self._active.pop(ticket_id, None)
t = threading.Thread(target=wrapper, args=args, daemon=True) t = threading.Thread(target=wrapper, args=args, daemon=True)
with self._lock: with self._lock:
self._active[ticket_id] = t self._active[ticket_id] = t
t.start() t.start()
return t return t
``` ```
**Key behaviors**: **Key behaviors**:
@@ -825,22 +825,22 @@ The `ConductorEngine` orchestrates ticket execution within a track:
```python ```python
class ConductorEngine: class ConductorEngine:
def __init__(self, track: Track, event_queue: Optional[SyncEventQueue] = None, def __init__(self, track: Track, event_queue: Optional[SyncEventQueue] = None,
auto_queue: bool = False) -> None: auto_queue: bool = False) -> None:
self.track = track self.track = track
self.event_queue = event_queue self.event_queue = event_queue
self.dag = TrackDAG(self.track.tickets) self.dag = TrackDAG(self.track.tickets)
self.engine = ExecutionEngine(self.dag, auto_queue=auto_queue) self.engine = ExecutionEngine(self.dag, auto_queue=auto_queue)
self.pool = WorkerPool(max_workers=4) self.pool = WorkerPool(max_workers=4)
self._abort_events: dict[str, threading.Event] = {} self._abort_events: dict[str, threading.Event] = {}
self._pause_event = threading.Event() self._pause_event = threading.Event()
self._tier_usage_lock = threading.Lock() self._tier_usage_lock = threading.Lock()
self.tier_usage = { self.tier_usage = {
"Tier 1": {"input": 0, "output": 0, "model": "gemini-3.1-pro-preview"}, "Tier 1": {"input": 0, "output": 0, "model": "gemini-3.1-pro-preview"},
"Tier 2": {"input": 0, "output": 0, "model": "gemini-3-flash-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 3": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite"},
"Tier 4": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite"}, "Tier 4": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite"},
} }
``` ```
**Main execution loop** (`run` method): **Main execution loop** (`run` method):
@@ -864,17 +864,17 @@ self._abort_events[ticket.id] = threading.Event()
# Worker checks abort at three points: # Worker checks abort at three points:
# 1. Before major work # 1. Before major work
if abort_event.is_set(): if abort_event.is_set():
ticket.status = "killed" ticket.status = "killed"
return "ABORTED" return "ABORTED"
# 2. Before tool execution (in clutch_callback) # 2. Before tool execution (in clutch_callback)
if abort_event.is_set(): if abort_event.is_set():
return False # Reject tool return False # Reject tool
# 3. After blocking send() returns # 3. After blocking send() returns
if abort_event.is_set(): if abort_event.is_set():
ticket.status = "killed" ticket.status = "killed"
return "ABORTED" return "ABORTED"
``` ```
--- ---
@@ -907,21 +907,21 @@ The `ProviderError` class provides structured error classification:
```python ```python
class ProviderError(Exception): class ProviderError(Exception):
def __init__(self, kind: str, provider: str, original: Exception): def __init__(self, kind: str, provider: str, original: Exception):
self.kind = kind # "quota" | "rate_limit" | "auth" | "balance" | "network" | "unknown" self.kind = kind # "quota" | "rate_limit" | "auth" | "balance" | "network" | "unknown"
self.provider = provider self.provider = provider
self.original = original self.original = original
def ui_message(self) -> str: def ui_message(self) -> str:
labels = { labels = {
"quota": "QUOTA EXHAUSTED", "quota": "QUOTA EXHAUSTED",
"rate_limit": "RATE LIMITED", "rate_limit": "RATE LIMITED",
"auth": "AUTH / API KEY ERROR", "auth": "AUTH / API KEY ERROR",
"balance": "BALANCE / BILLING ERROR", "balance": "BALANCE / BILLING ERROR",
"network": "NETWORK / CONNECTION ERROR", "network": "NETWORK / CONNECTION ERROR",
"unknown": "API ERROR", "unknown": "API ERROR",
} }
return f"[{self.provider.upper()} {labels.get(self.kind, 'API ERROR')}]\n\n{self.original}" return f"[{self.provider.upper()} {labels.get(self.kind, 'API ERROR')}]\n\n{self.original}"
``` ```
### Error Recovery Patterns ### Error Recovery Patterns
@@ -944,29 +944,29 @@ class ProviderError(Exception):
**Gemini (40% threshold)**: **Gemini (40% threshold)**:
```python ```python
if total_in > _GEMINI_MAX_INPUT_TOKENS * 0.4: if total_in > _GEMINI_MAX_INPUT_TOKENS * 0.4:
while len(hist) > 4 and total_in > _GEMINI_MAX_INPUT_TOKENS * 0.3: while len(hist) > 4 and total_in > _GEMINI_MAX_INPUT_TOKENS * 0.3:
# Drop oldest message pairs # Drop oldest message pairs
hist.pop(0) # Assistant hist.pop(0) # Assistant
hist.pop(0) # User hist.pop(0) # User
``` ```
**Anthropic (180K limit)**: **Anthropic (180K limit)**:
```python ```python
def _trim_anthropic_history(system_blocks, history): def _trim_anthropic_history(system_blocks, history):
est = _estimate_prompt_tokens(system_blocks, history) est = _estimate_prompt_tokens(system_blocks, history)
while len(history) > 3 and est > _ANTHROPIC_MAX_PROMPT_TOKENS: while len(history) > 3 and est > _ANTHROPIC_MAX_PROMPT_TOKENS:
# Drop turn pairs, preserving tool_result chains # Drop turn pairs, preserving tool_result chains
... ...
``` ```
### Tool Output Budget ### Tool Output Budget
```python ```python
_MAX_TOOL_OUTPUT_BYTES: int = 500_000 # 500KB cumulative _MAX_TOOL_OUTPUT_BYTES: int = 500_000 # 500KB cumulative
if _cumulative_tool_bytes > _MAX_TOOL_OUTPUT_BYTES: if _cumulative_tool_bytes > _MAX_TOOL_OUTPUT_BYTES:
# Inject warning, force final answer # Inject warning, force final answer
parts.append("SYSTEM WARNING: Cumulative tool output exceeded 500KB budget.") parts.append("SYSTEM WARNING: Cumulative tool output exceeded 500KB budget.")
``` ```
### AST Cache (file_cache.py) ### AST Cache (file_cache.py)
@@ -975,17 +975,17 @@ if _cumulative_tool_bytes > _MAX_TOOL_OUTPUT_BYTES:
_ast_cache: Dict[str, Tuple[float, tree_sitter.Tree]] = {} _ast_cache: Dict[str, Tuple[float, tree_sitter.Tree]] = {}
def get_cached_tree(self, path: Optional[str], code: str) -> tree_sitter.Tree: def get_cached_tree(self, path: Optional[str], code: str) -> tree_sitter.Tree:
mtime = p.stat().st_mtime if p.exists() else 0.0 mtime = p.stat().st_mtime if p.exists() else 0.0
if path in _ast_cache: if path in _ast_cache:
cached_mtime, tree = _ast_cache[path] cached_mtime, tree = _ast_cache[path]
if cached_mtime == mtime: if cached_mtime == mtime:
return tree return tree
# Parse and cache with simple LRU (max 10 entries) # Parse and cache with simple LRU (max 10 entries)
if len(_ast_cache) >= 10: if len(_ast_cache) >= 10:
del _ast_cache[next(iter(_ast_cache))] del _ast_cache[next(iter(_ast_cache))]
tree = self.parse(code) tree = self.parse(code)
_ast_cache[path] = (mtime, tree) _ast_cache[path] = (mtime, tree)
return tree return tree
``` ```
--- ---
+48 -48
View File
@@ -24,15 +24,15 @@ This is one of the most-touched modules in the project. After the nagent_review,
``` ```
aggregate.run(config, aggregation_strategy) aggregate.run(config, aggregation_strategy)
├─ find_next_increment(output_dir, namespace) # next file number for output ├─ find_next_increment(output_dir, namespace) # next file number for output
├─ build_file_items(base_dir, files) # read + view-mode transform ├─ build_file_items(base_dir, files) # read + view-mode transform
├─ build_markdown_from_items(file_items, ...) # compose sections ├─ build_markdown_from_items(file_items, ...) # compose sections
├─ ## Files (or Files (Summary) or Files (Tier 3 - Focused)) │ ├─ ## Files (or Files (Summary) or Files (Tier 3 - Focused))
└─ _build_files_section_from_items OR summarize.build_summary_markdown └─ _build_files_section_from_items OR summarize.build_summary_markdown
├─ ## Screenshots (if any) │ ├─ ## Screenshots (if any)
├─ ## Beads Mode: Progress Track (if execution_mode == "beads") │ ├─ ## Beads Mode: Progress Track (if execution_mode == "beads")
└─ ## Discussion History (if any) │ └─ ## Discussion History (if any)
└─ output_file.write_text(markdown) └─ output_file.write_text(markdown)
``` ```
The **output** is a markdown file at `{output_dir}/{namespace}_{NNN}.md` where `NNN` is a zero-padded increment. The pipeline does not *send* the markdown — that's the AI client's job. The pipeline *produces* the markdown. The **output** is a markdown file at `{output_dir}/{namespace}_{NNN}.md` where `NNN` is a zero-padded increment. The pipeline does not *send* the markdown — that's the AI client's job. The pipeline *produces* the markdown.
@@ -54,11 +54,11 @@ The **return value** is `(markdown: str, output_file: Path, file_items: list[dic
**Implementation:** `aggregate.py:330-346 build_markdown_from_items`. The three-way dispatch is at lines 335-339: **Implementation:** `aggregate.py:330-346 build_markdown_from_items`. The three-way dispatch is at lines 335-339:
```python ```python
if aggregation_strategy == "summarize": parts.append("## Files (Summary)\n\n" + summarize.build_summary_markdown(file_items)) if aggregation_strategy == "summarize": parts.append("## Files (Summary)\n\n" + summarize.build_summary_markdown(file_items))
elif aggregation_strategy == "full": parts.append("## Files\n\n" + _build_files_section_from_items(file_items)) elif aggregation_strategy == "full": parts.append("## Files\n\n" + _build_files_section_from_items(file_items))
else: # auto else: # auto
if summary_only: parts.append("## Files (Summary)\n\n" + summarize.build_summary_markdown(file_items)) if summary_only: parts.append("## Files (Summary)\n\n" + summarize.build_summary_markdown(file_items))
else: parts.append("## Files\n\n" + _build_files_section_from_items(file_items)) else: parts.append("## Files\n\n" + _build_files_section_from_items(file_items))
``` ```
The `auto` strategy is the *only* one that respects `config.project.summary_only`; the other two are explicit overrides. Personas can also set `aggregation_strategy` (per `guide_personas.md`), and a persona-set strategy overrides the config-level setting. The `auto` strategy is the *only* one that respects `config.project.summary_only`; the other two are explicit overrides. Personas can also set `aggregation_strategy` (per `guide_personas.md`), and a persona-set strategy overrides the config-level setting.
@@ -92,16 +92,16 @@ The `auto` strategy is the *only* one that respects `config.project.summary_only
```python ```python
@dataclass @dataclass
class FileItem: class FileItem:
path: str # the artifact identity (path-keyed, no inode) path: str # the artifact identity (path-keyed, no inode)
auto_aggregate: bool = True # include in auto-aggregation? (skip in build_*_from_items if False) auto_aggregate: bool = True # include in auto-aggregation? (skip in build_*_from_items if False)
force_full: bool = False # bypass view_mode; force raw content force_full: bool = False # bypass view_mode; force raw content
view_mode: str = 'full' # one of: full, summary, skeleton, outline, masked, custom, none view_mode: str = 'full' # one of: full, summary, skeleton, outline, masked, custom, none
selected: bool = False # for batch operations (the Context Panel multi-select) selected: bool = False # for batch operations (the Context Panel multi-select)
ast_signatures: bool = False # include only signatures (skeleton-equivalent shortcut) ast_signatures: bool = False # include only signatures (skeleton-equivalent shortcut)
ast_definitions: bool = False # include only definitions (skeleton-equivalent shortcut) ast_definitions: bool = False # include only definitions (skeleton-equivalent shortcut)
ast_mask: dict[str, str] # per-symbol mask: {symbol_path: 'def'|'sig'|'hide'} (from Structural File Editor) ast_mask: dict[str, str] # per-symbol mask: {symbol_path: 'def'|'sig'|'hide'} (from Structural File Editor)
custom_slices: list[dict] # Fuzzy Anchor slices: {start_line, end_line, tag, comment, ...} custom_slices: list[dict] # Fuzzy Anchor slices: {start_line, end_line, tag, comment, ...}
injected_at: Optional[float] # timestamp of last injection injected_at: Optional[float] # timestamp of last injection
``` ```
The 9 fields are *all* serialized by `to_dict()` and *all* deserialized by `from_dict()` (with `.get(..., default)` for forward compatibility). The dataclass is round-trip-safe through TOML. The 9 fields are *all* serialized by `to_dict()` and *all* deserialized by `from_dict()` (with `.get(..., default)` for forward compatibility). The dataclass is round-trip-safe through TOML.
@@ -114,13 +114,13 @@ A `custom_slices` entry is `{start_line, end_line, tag, comment, ...}` (plus Fuz
```python ```python
{ {
"start_line": int, # 1-based original line "start_line": int, # 1-based original line
"end_line": int, # 1-based original line (inclusive) "end_line": int, # 1-based original line (inclusive)
"tag": str|None, # human label, defaults to None "tag": str|None, # human label, defaults to None
"comment": str|None, # human comment, defaults to None "comment": str|None, # human comment, defaults to None
"content_hash": str, # SHA-256 of the slice content (for Fuzzy Anchor stability) "content_hash": str, # SHA-256 of the slice content (for Fuzzy Anchor stability)
"anchor_lines": [str, ...],# surrounding context for re-resolution "anchor_lines": [str, ...],# surrounding context for re-resolution
# plus the original positioning metadata # plus the original positioning metadata
} }
``` ```
@@ -144,10 +144,10 @@ Multiple slices in a file are joined with `\n\n`.
```python ```python
@dataclass @dataclass
class ContextPreset: class ContextPreset:
name: str # the preset name (used as TOML key) name: str # the preset name (used as TOML key)
files: list[ContextFileEntry] = field(default_factory=list) files: list[ContextFileEntry] = field(default_factory=list)
screenshots: list[str] = field(default_factory=list) screenshots: list[str] = field(default_factory=list)
description: str = "" description: str = ""
``` ```
`ContextFileEntry` is a `FileItem` (or a string path that's promoted to a `FileItem` on load). The `description` is a human-readable label for the preset list. `ContextFileEntry` is a `FileItem` (or a string path that's promoted to a `FileItem` on load). The `description` is a human-readable label for the preset list.
@@ -170,16 +170,16 @@ class ContextPreset:
```python ```python
def build_discussion_section(history: list[Any]) -> str: def build_discussion_section(history: list[Any]) -> str:
sections = [] sections = []
for i, entry in enumerate(history, start=1): for i, entry in enumerate(history, start=1):
if isinstance(entry, dict): if isinstance(entry, dict):
role = entry.get("role", "Unknown") role = entry.get("role", "Unknown")
content = entry.get("content", "").strip() content = entry.get("content", "").strip()
text = f"{role}: {content}" text = f"{role}: {content}"
else: else:
text = str(entry).strip() text = str(entry).strip()
sections.append(f"### Discussion Excerpt {i}\n\n{text}") sections.append(f"### Discussion Excerpt {i}\n\n{text}")
return "\n\n---\n\n".join(sections) return "\n\n---\n\n".join(sections)
``` ```
The section handles *both* legacy `list[str]` (e.g. `["User: ...", "AI: ..."]`) and the new `list[dict]` shape (`[{"role": ..., "content": ...}, ...]`). The dict shape is what's persisted by `_flush_disc_entries_to_project` (per `app_controller.py:3225-3240`) and what's stored in the new format. The section handles *both* legacy `list[str]` (e.g. `["User: ...", "AI: ..."]`) and the new `list[dict]` shape (`[{"role": ..., "content": ...}, ...]`). The dict shape is what's persisted by `_flush_disc_entries_to_project` (per `app_controller.py:3225-3240`) and what's stored in the new format.
@@ -231,7 +231,7 @@ For Tier 3, `force_full` is treated as a *focus flag*:
```python ```python
if is_focus or tier == 3 or force_full: if is_focus or tier == 3 or force_full:
# full content, no skeleton # full content, no skeleton
``` ```
So a `force_full=True` file in a Tier 3 worker context is treated as a focus file and rendered in full. So a `force_full=True` file in a Tier 3 worker context is treated as a focus file and rendered in full.
@@ -244,8 +244,8 @@ So a `force_full=True` file in a Tier 3 worker context is treated as a focus fil
```python ```python
for item in file_items: for item in file_items:
if not item.get("auto_aggregate", True): continue if not item.get("auto_aggregate", True): continue
# ... build section # ... build section
``` ```
Use case: the file is in the `files` list for the AI's *awareness* (e.g. "you can read it via `read_file`") but should not be inlined. The file's `mtime` and `view_mode` are still tracked; the file is *omitted* from the rendered markdown. Use case: the file is in the `files` list for the AI's *awareness* (e.g. "you can read it via `read_file`") but should not be inlined. The file's `mtime` and `view_mode` are still tracked; the file is *omitted* from the rendered markdown.
@@ -384,7 +384,7 @@ For very large codebases (1000+ files), the bottleneck is the tree-sitter parsin
- **FileItem schema:** `src/project_files.py:FileItem` (moved out of `src/models.py`) - **FileItem schema:** `src/project_files.py:FileItem` (moved out of `src/models.py`)
- **ContextPreset schema:** `src/context_presets.py:ContextPreset` (moved out of `src/models.py`) - **ContextPreset schema:** `src/context_presets.py:ContextPreset` (moved out of `src/models.py`)
- **ContextPresetManager:** `src/context_presets.py` (30 lines) - **ContextPresetManager:** `src/context_presets.py` (30 lines)
- **AI client consumption:** `src/ai_client.py:_send_<provider>` × 8 (gemini, anthropic, gemini_cli, deepseek, minimax, qwen, grok, llama), see `guide_ai_client.md` - **AI client consumption:** `src/ai_client.py:_send_<provider>` × 8 (gemini, anthropic, deepseek, minimax, qwen, grok, llama), see `guide_ai_client.md`
- **Tier 3 worker consumption:** `src/multi_agent_conductor.py:run_worker_lifecycle`, see `guide_multi_agent_conductor.md` - **Tier 3 worker consumption:** `src/multi_agent_conductor.py:run_worker_lifecycle`, see `guide_multi_agent_conductor.md`
- **Per-file curation features:** `guide_context_curation.md` (Fuzzy Anchors, AST Inspector, Granular AST Control) - **Per-file curation features:** `guide_context_curation.md` (Fuzzy Anchors, AST Inspector, Granular AST Control)
- **Cache strategy:** `guide_architecture.md §"Cache Hit Strategy"`, `guide_ai_client.md §"Caching"` - **Cache strategy:** `guide_architecture.md §"Cache Hit Strategy"`, `guide_ai_client.md §"Caching"`
+4 -4
View File
@@ -19,12 +19,12 @@ The dataclass definitions, `DEFAULT_TOOL_CATEGORIES`, the `__getattr__` shim, an
```python ```python
from src.mma import TrackMetadata from src.mma import TrackMetadata
Metadata = TrackMetadata # legacy class name re-export Metadata = TrackMetadata # legacy class name re-export
def __getattr__(name: str) -> Any: def __getattr__(name: str) -> Any:
if name == "PROVIDERS": if name == "PROVIDERS":
from src import ai_client from src import ai_client
return ai_client.PROVIDERS return ai_client.PROVIDERS
raise AttributeError(f"module {__name__!r} has no attribute {name!r}") raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
``` ```
@@ -56,7 +56,7 @@ The old "one registry to look at" goal is now achieved by **per-system files**.
| Constant | Current location | Notes | | Constant | Current location | Notes |
|---|---|---| |---|---|---|
| `PROVIDERS` | `src/ai_client.py` (re-exported by `src/models.py` via lazy `__getattr__`) | `List[str]` of 8 providers: `gemini`, `anthropic`, `gemini_cli`, `deepseek`, `minimax`, `qwen`, `grok`, `llama` | | `PROVIDERS` | `src/ai_client.py` (re-exported by `src/models.py` via lazy `__getattr__`) | `List[str]` of 7 providers: `gemini`, `anthropic`, `gemini_cli`, `deepseek`, `minimax`, `qwen`, `grok`, `llama` |
| `DEFAULT_TOOL_CATEGORIES` | `src/ai_client.py` | The canonical grouping of the MCP tool registry for the UI's category filter | | `DEFAULT_TOOL_CATEGORIES` | `src/ai_client.py` | The canonical grouping of the MCP tool registry for the UI's category filter |
| Tool names (formerly `AGENT_TOOL_NAMES`) | `src/mcp_tool_specs.py:_REGISTRY` + `mcp_tool_specs.tool_names()` | 45 tools. Re-exported as `mcp_client.TOOL_NAMES` for backward compat | | Tool names (formerly `AGENT_TOOL_NAMES`) | `src/mcp_tool_specs.py:_REGISTRY` + `mcp_tool_specs.tool_names()` | 45 tools. Re-exported as `mcp_client.TOOL_NAMES` for backward compat |
| `DEFAULT_TIER_PERSONAS` | `src/mma_prompts.py` | MMA tier → default persona mapping | | `DEFAULT_TIER_PERSONAS` | `src/mma_prompts.py` | MMA tier → default persona mapping |
+58 -58
View File
@@ -29,13 +29,13 @@ Defined in `tests/conftest.py`, this session-scoped fixture manages the lifecycl
```python ```python
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def live_gui(request) -> Generator["_LiveGuiHandle", None, None]: def live_gui(request) -> Generator["_LiveGuiHandle", None, None]:
process = subprocess.Popen( process = subprocess.Popen(
["uv", "run", "python", "-u", gui_script, "--enable-test-hooks"], ["uv", "run", "python", "-u", gui_script, "--enable-test-hooks"],
stdout=log_file, stderr=log_file, text=True, stdout=log_file, stderr=log_file, text=True,
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if os.name == 'nt' else 0 creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if os.name == 'nt' else 0
) )
# ... (readiness polling + xdist coordination) ... # ... (readiness polling + xdist coordination) ...
yield _LiveGuiHandle(process, gui_script, workspace=temp_workspace) yield _LiveGuiHandle(process, gui_script, workspace=temp_workspace)
``` ```
- **`-u` flag**: Disables output buffering for real-time log capture. - **`-u` flag**: Disables output buffering for real-time log capture.
@@ -45,13 +45,13 @@ def live_gui(request) -> Generator["_LiveGuiHandle", None, None]:
**Readiness polling:** **Readiness polling:**
```python ```python
max_retries = 15 # seconds max_retries = 15 # seconds
while time.time() - start_time < max_retries: while time.time() - start_time < max_retries:
response = requests.get("http://127.0.0.1:8999/status", timeout=0.5) response = requests.get("http://127.0.0.1:8999/status", timeout=0.5)
if response.status_code == 200: if response.status_code == 200:
ready = True; break ready = True; break
if process.poll() is not None: break # Process died early if process.poll() is not None: break # Process died early
time.sleep(0.5) time.sleep(0.5)
``` ```
Polls `GET /status` every 500ms for up to 15 seconds. Checks `process.poll()` each iteration to detect early crashes (avoids waiting the full timeout if the GUI exits). Pre-check: tests if port 8999 is already occupied. Polls `GET /status` every 500ms for up to 15 seconds. Checks `process.poll()` each iteration to detect early crashes (avoids waiting the full timeout if the GUI exits). Pre-check: tests if port 8999 is already occupied.
@@ -62,11 +62,11 @@ Polls `GET /status` every 500ms for up to 15 seconds. Checks `process.poll()` ea
```python ```python
finally: finally:
client = ApiHookClient() client = ApiHookClient()
client.reset_session() # Clean GUI state before killing client.reset_session() # Clean GUI state before killing
time.sleep(0.5) time.sleep(0.5)
kill_process_tree(process.pid) kill_process_tree(process.pid)
log_file.close() log_file.close()
``` ```
Sends `reset_session()` via `ApiHookClient` before killing to prevent stale state files. Sends `reset_session()` via `ApiHookClient` before killing to prevent stale state files.
@@ -91,9 +91,9 @@ Sends `reset_session()` via `ApiHookClient` before killing to prevent stale stat
```python ```python
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def reset_ai_client() -> Generator[None, None, None]: def reset_ai_client() -> Generator[None, None, None]:
ai_client.reset_session() ai_client.reset_session()
ai_client.set_provider("gemini", "gemini-2.5-flash-lite") ai_client.set_provider("gemini", "gemini-2.5-flash-lite")
yield yield
``` ```
Runs automatically before every test. Resets the `ai_client` module state and defaults to a safe model, preventing state pollution between tests. Runs automatically before every test. Resets the `ai_client` module state and defaults to a safe model, preventing state pollution between tests.
@@ -103,9 +103,9 @@ Runs automatically before every test. Resets the `ai_client` module state and de
```python ```python
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def isolate_workspace(tmp_path_factory, monkeypatch) -> Generator[None, None, None]: def isolate_workspace(tmp_path_factory, monkeypatch) -> Generator[None, None, None]:
# Redirects the path resolution layer to a temp directory # Redirects the path resolution layer to a temp directory
# Prevents tests from writing to the user's actual project # Prevents tests from writing to the user's actual project
... ...
``` ```
This autouse fixture ensures every test runs against an isolated `tmp_path` workspace. It `monkeypatch`-es `src.paths` so that any code path resolving a project directory (e.g., `manual_slop.toml` lookup, conductor directory resolution, log directory) is redirected to a fresh temp directory per test. Without this, tests could mutate the user's actual `manual_slop.toml` or conductor tracks directory. This autouse fixture ensures every test runs against an isolated `tmp_path` workspace. It `monkeypatch`-es `src.paths` so that any code path resolving a project directory (e.g., `manual_slop.toml` lookup, conductor directory resolution, log directory) is redirected to a fresh temp directory per test. Without this, tests could mutate the user's actual `manual_slop.toml` or conductor tracks directory.
@@ -117,8 +117,8 @@ This is the primary mechanism for satisfying the **Artifact Isolation** rule in
```python ```python
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def reset_paths() -> Generator[None, None, None]: def reset_paths() -> Generator[None, None, None]:
# Forces `src/paths.py` to re-resolve from environment / config on next access # Forces `src/paths.py` to re-resolve from environment / config on next access
... ...
``` ```
Pairs with `isolate_workspace` to fully reset the path subsystem. After a test that creates a project config, the next test gets a clean slate. Pairs with `isolate_workspace` to fully reset the path subsystem. After a test that creates a project config, the next test gets a clean slate.
@@ -147,11 +147,11 @@ Structured diagnostic logging for test telemetry:
```python ```python
class VerificationLogger: class VerificationLogger:
def __init__(self, test_name: str, script_name: str): def __init__(self, test_name: str, script_name: str):
self.logs_dir = Path(f"logs/test/{datetime.now().strftime('%Y%m%d_%H%M%S')}") self.logs_dir = Path(f"logs/test/{datetime.now().strftime('%Y%m%d_%H%M%S')}")
def log_state(self, field: str, before: Any, after: Any, delta: Any = None) def log_state(self, field: str, before: Any, after: Any, delta: Any = None)
def finalize(self, description: str, status: str, result_msg: str) def finalize(self, description: str, status: str, result_msg: str)
``` ```
Output format: fixed-width column table (`Field | Before | After | Delta`) written to `logs/test/<timestamp>/<script_name>.txt`. Dual output: file + tagged stdout lines for CI visibility. Output format: fixed-width column table (`Field | Before | After | Delta`) written to `logs/test/<timestamp>/<script_name>.txt`. Dual output: file + tagged stdout lines for CI visibility.
@@ -191,12 +191,12 @@ Enters an epic description and triggers planning. The GUI invokes the LLM (which
```python ```python
for _ in range(60): for _ in range(60):
status = client.get_mma_status() status = client.get_mma_status()
if status.get('pending_mma_spawn_approval'): client.click('btn_approve_spawn') if status.get('pending_mma_spawn_approval'): client.click('btn_approve_spawn')
elif status.get('pending_mma_step_approval'): client.click('btn_approve_mma_step') elif status.get('pending_mma_step_approval'): client.click('btn_approve_mma_step')
elif status.get('pending_tool_approval'): client.click('btn_approve_tool') elif status.get('pending_tool_approval'): client.click('btn_approve_tool')
if status.get('proposed_tracks') and len(status['proposed_tracks']) > 0: break if status.get('proposed_tracks') and len(status['proposed_tracks']) > 0: break
time.sleep(1) time.sleep(1)
``` ```
The **approval automation** is a critical pattern repeated in every polling loop. The MMA engine has three approval gates: The **approval automation** is a critical pattern repeated in every polling loop. The MMA engine has three approval gates:
@@ -235,9 +235,9 @@ Polls until `mma_status == 'running'` or `'done'`. Continues auto-approving all
```python ```python
streams = status.get('mma_streams', {}) streams = status.get('mma_streams', {})
if any("Tier 3" in k for k in streams.keys()): if any("Tier 3" in k for k in streams.keys()):
tier3_key = [k for k in streams.keys() if "Tier 3" in k][0] tier3_key = [k for k in streams.keys() if "Tier 3" in k][0]
if "SUCCESS: Mock Tier 3 worker" in streams[tier3_key]: if "SUCCESS: Mock Tier 3 worker" in streams[tier3_key]:
streams_found = True streams_found = True
``` ```
Verifies that `mma_streams` contains a key with "Tier 3" and the value contains the exact mock output string. Verifies that `mma_streams` contains a key with "Tier 3" and the value contains the exact mock output string.
@@ -262,16 +262,16 @@ A fake Gemini CLI executable that replaces the real `gemini` binary during integ
**Input mechanism:** **Input mechanism:**
```python ```python
prompt = sys.stdin.read() # Primary: prompt via stdin prompt = sys.stdin.read() # Primary: prompt via stdin
sys.argv # Secondary: management command detection sys.argv # Secondary: management command detection
os.environ.get('GEMINI_CLI_HOOK_CONTEXT') # Tertiary: environment variable os.environ.get('GEMINI_CLI_HOOK_CONTEXT') # Tertiary: environment variable
``` ```
**Management command bypass:** **Management command bypass:**
```python ```python
if len(sys.argv) > 1 and sys.argv[1] in ["mcp", "extensions", "skills", "hooks"]: if len(sys.argv) > 1 and sys.argv[1] in ["mcp", "extensions", "skills", "hooks"]:
return # Silent exit return # Silent exit
``` ```
**Response routing** — keyword matching on stdin content: **Response routing** — keyword matching on stdin content:
@@ -390,22 +390,22 @@ The headless service uses the **Remote Confirmation Protocol** for HITL: when an
```python ```python
class ASTParser: class ASTParser:
def __init__(self, language: str = "python"): def __init__(self, language: str = "python"):
self.language = tree_sitter.Language(tree_sitter_python.language()) self.language = tree_sitter.Language(tree_sitter_python.language())
self.parser = tree_sitter.Parser(self.language) self.parser = tree_sitter.Parser(self.language)
def parse(self, code: str) -> tree_sitter.Tree def parse(self, code: str) -> tree_sitter.Tree
def get_skeleton(self, code: str, path: str = "") -> str def get_skeleton(self, code: str, path: str = "") -> str
def get_curated_view(self, code: str, path: str = "") -> str def get_curated_view(self, code: str, path: str = "") -> str
def get_targeted_view(self, code: str, symbols: List[str], path: str = "") -> str def get_targeted_view(self, code: str, symbols: List[str], path: str = "") -> str
``` ```
**`get_skeleton` algorithm:** **`get_skeleton` algorithm:**
1. Parse code to tree-sitter AST. 1. Parse code to tree-sitter AST.
2. Walk all `function_definition` nodes. 2. Walk all `function_definition` nodes.
3. For each body (`block` node): 3. For each body (`block` node):
- If first non-comment child is a docstring: preserve docstring, replace rest with `...`. - If first non-comment child is a docstring: preserve docstring, replace rest with `...`.
- Otherwise: replace entire body with `...`. - Otherwise: replace entire body with `...`.
4. Apply edits in reverse byte order (maintains valid offsets). 4. Apply edits in reverse byte order (maintains valid offsets).
**`get_curated_view` algorithm:** **`get_curated_view` algorithm:**
@@ -428,10 +428,10 @@ Token-efficient structural descriptions without AI calls:
```python ```python
_SUMMARISERS: dict[str, Callable] = { _SUMMARISERS: dict[str, Callable] = {
".py": _summarise_python, # imports, classes, methods, functions, constants ".py": _summarise_python, # imports, classes, methods, functions, constants
".toml": _summarise_toml, # table keys + array lengths ".toml": _summarise_toml, # table keys + array lengths
".md": _summarise_markdown, # h1-h3 headings ".md": _summarise_markdown, # h1-h3 headings
".ini": _summarise_generic, # line count + preview ".ini": _summarise_generic, # line count + preview
} }
``` ```
@@ -455,8 +455,8 @@ functions: summarise_file, build_summary_markdown
```python ```python
class CodeOutliner: class CodeOutliner:
def __init__(self) -> None: ... def __init__(self) -> None: ...
def outline(self, code: str) -> str: ... def outline(self, code: str) -> str: ...
def get_outline(path: Path, code: str) -> str: ... def get_outline(path: Path, code: str) -> str: ...
``` ```
+66 -66
View File
@@ -11,9 +11,9 @@ The AI's ability to interact with the filesystem is mediated by a three-layer se
### Global State ### Global State
```python ```python
_allowed_paths: set[Path] = set() # Explicit file allowlist (resolved absolutes) _allowed_paths: set[Path] = set() # Explicit file allowlist (resolved absolutes)
_base_dirs: set[Path] = set() # Directory roots for containment checks _base_dirs: set[Path] = set() # Directory roots for containment checks
_primary_base_dir: Path | None = None # Used for resolving relative paths _primary_base_dir: Path | None = None # Used for resolving relative paths
perf_monitor_callback: Optional[Callable[[], dict[str, Any]]] = None perf_monitor_callback: Optional[Callable[[], dict[str, Any]]] = None
``` ```
@@ -61,7 +61,7 @@ The `dispatch` function (`mcp_client.py:1322`) is a flat if/elif chain mapping 4
| Tool | Parameters | Description | | Tool | Parameters | Description |
|---|---|---| |---|---|---|
| `read_file` | `path` | UTF-8 file content extraction | | `read_file` | `path` | UTF-8 file content extraction |
| `list_directory` | `path` | Compact table: `[file/dir] name size`. Applies blacklist filter to entries. | | `list_directory` | `path` | Compact table: `[file/dir] name size`. Applies blacklist filter to entries. |
| `search_files` | `path`, `pattern` | Glob pattern matching within an allowed directory. Applies blacklist filter. | | `search_files` | `path`, `pattern` | Glob pattern matching within an allowed directory. Applies blacklist filter. |
| `get_file_slice` | `path`, `start_line`, `end_line` | Returns specific line range (1-based, inclusive) | | `get_file_slice` | `path`, `start_line`, `end_line` | Returns specific line range (1-based, inclusive) |
| `set_file_slice` | `path`, `start_line`, `end_line`, `new_content` | Replaces a line range with new content (surgical edit) | | `set_file_slice` | `path`, `start_line`, `end_line`, `new_content` | Replaces a line range with new content (surgical edit) |
@@ -166,28 +166,28 @@ See [guide_beads.md](guide_beads.md) (placeholder; written in Task 10) for the f
**AST-based read tools** follow this pattern: **AST-based read tools** follow this pattern:
```python ```python
def py_get_skeleton(path: str) -> str: def py_get_skeleton(path: str) -> str:
p, err = _resolve_and_check(path) p, err = _resolve_and_check(path)
if err: return err if err: return err
if not p.exists(): return f"ERROR: file not found: {path}" if not p.exists(): return f"ERROR: file not found: {path}"
if not p.is_file() or p.suffix != ".py": return f"ERROR: not a python file: {path}" if not p.is_file() or p.suffix != ".py": return f"ERROR: not a python file: {path}"
from file_cache import ASTParser from file_cache import ASTParser
code = p.read_text(encoding="utf-8") code = p.read_text(encoding="utf-8")
parser = ASTParser("python") parser = ASTParser("python")
return parser.get_skeleton(code) return parser.get_skeleton(code)
``` ```
**AST-based write tools** use stdlib `ast` (not tree-sitter) to locate symbols, then delegate to `set_file_slice`: **AST-based write tools** use stdlib `ast` (not tree-sitter) to locate symbols, then delegate to `set_file_slice`:
```python ```python
def py_update_definition(path: str, name: str, new_content: str) -> str: def py_update_definition(path: str, name: str, new_content: str) -> str:
p, err = _resolve_and_check(path) p, err = _resolve_and_check(path)
if err: return err if err: return err
code = p.read_text(encoding="utf-8").lstrip(chr(0xFEFF)) # Strip BOM code = p.read_text(encoding="utf-8").lstrip(chr(0xFEFF)) # Strip BOM
tree = ast.parse(code) tree = ast.parse(code)
node = _get_symbol_node(tree, name) # Walks AST for matching node node = _get_symbol_node(tree, name) # Walks AST for matching node
if not node: return f"ERROR: could not find definition '{name}'" if not node: return f"ERROR: could not find definition '{name}'"
start = getattr(node, "lineno") start = getattr(node, "lineno")
end = getattr(node, "end_lineno") end = getattr(node, "end_lineno")
return set_file_slice(path, start, end, new_content) return set_file_slice(path, start, end, new_content)
``` ```
The `_get_symbol_node` helper supports dot notation (`ClassName.method_name`) by first finding the class, then searching its body for the method. The `_get_symbol_node` helper supports dot notation (`ClassName.method_name`) by first finding the class, then searching its body for the method.
@@ -200,19 +200,19 @@ Tools can be executed concurrently via `async_dispatch`:
```python ```python
async def async_dispatch(tool_name: str, tool_input: dict[str, Any]) -> str: async def async_dispatch(tool_name: str, tool_input: dict[str, Any]) -> str:
"""Dispatch an MCP tool call asynchronously.""" """Dispatch an MCP tool call asynchronously."""
return await asyncio.to_thread(dispatch, tool_name, tool_input) return await asyncio.to_thread(dispatch, tool_name, tool_input)
``` ```
In `ai_client.py`, multiple tool calls within a single AI turn are executed in parallel: In `ai_client.py`, multiple tool calls within a single AI turn are executed in parallel:
```python ```python
async def _execute_tool_calls_concurrently(calls, base_dir, ...): async def _execute_tool_calls_concurrently(calls, base_dir, ...):
tasks = [] tasks = []
for fc in calls: for fc in calls:
tasks.append(_execute_single_tool_call_async(name, args, ...)) tasks.append(_execute_single_tool_call_async(name, args, ...))
results = await asyncio.gather(*tasks) results = await asyncio.gather(*tasks)
return results return results
``` ```
This significantly reduces latency when the AI makes multiple independent file reads in a single turn. This significantly reduces latency when the AI makes multiple independent file reads in a single turn.
@@ -229,16 +229,16 @@ Manual Slop exposes a REST-based IPC interface on `127.0.0.1:8999` using Python'
```python ```python
class HookServerInstance(ThreadingHTTPServer): class HookServerInstance(ThreadingHTTPServer):
app: Any # Reference to main App instance app: Any # Reference to main App instance
class HookHandler(BaseHTTPRequestHandler): class HookHandler(BaseHTTPRequestHandler):
# Accesses self.server.app for all state # Accesses self.server.app for all state
class HookServer: class HookServer:
app: Any app: Any
port: int = 8999 port: int = 8999
server: HookServerInstance | None server: HookServerInstance | None
thread: threading.Thread | None thread: threading.Thread | None
``` ```
**Start conditions**: Only starts if `app.test_hooks_enabled == True` OR current provider is `'gemini_cli'`. Otherwise `start()` silently returns. **Start conditions**: Only starts if `app.test_hooks_enabled == True` OR current provider is `'gemini_cli'`. Otherwise `start()` silently returns.
@@ -274,20 +274,20 @@ This ensures all state reads happen on the GUI main thread during `_process_pend
```python ```python
{ {
"mma_status": str, # "idle" | "planning" | "executing" | "done" "mma_status": str, # "idle" | "planning" | "executing" | "done"
"ai_status": str, # "idle" | "sending..." | etc. "ai_status": str, # "idle" | "sending..." | etc.
"active_tier": str | None, "active_tier": str | None,
"active_track": str, # Track ID or raw value "active_track": str, # Track ID or raw value
"active_tickets": list, # Serialized ticket dicts "active_tickets": list, # Serialized ticket dicts
"mma_step_mode": bool, "mma_step_mode": bool,
"pending_tool_approval": bool, # _pending_ask_dialog "pending_tool_approval": bool, # _pending_ask_dialog
"pending_mma_step_approval": bool, # _pending_mma_approval is not None "pending_mma_step_approval": bool, # _pending_mma_approval is not None
"pending_mma_spawn_approval": bool, # _pending_mma_spawn is not None "pending_mma_spawn_approval": bool, # _pending_mma_spawn is not None
"pending_approval": bool, # Backward compat: step OR tool "pending_approval": bool, # Backward compat: step OR tool
"pending_spawn": bool, # Alias for spawn approval "pending_spawn": bool, # Alias for spawn approval
"tracks": list, "tracks": list,
"proposed_tracks": list, "proposed_tracks": list,
"mma_streams": dict, # {stream_id: output_text} "mma_streams": dict, # {stream_id: output_text}
} }
``` ```
@@ -295,9 +295,9 @@ This ensures all state reads happen on the GUI main thread during `_process_pend
```python ```python
{ {
"thinking": bool, # ai_status in ["sending...", "running powershell..."] "thinking": bool, # ai_status in ["sending...", "running powershell..."]
"live": bool, # ai_status in ["running powershell...", "fetching url...", ...] "live": bool, # ai_status in ["running powershell...", "fetching url...", ...]
"prior": bool, # app.is_viewing_prior_session "prior": bool, # app.is_viewing_prior_session
} }
``` ```
@@ -340,7 +340,7 @@ The counterpart `/api/ask/respond`:
```python ```python
class ApiHookClient: class ApiHookClient:
def __init__(self, base_url="http://127.0.0.1:8999", max_retries=5, retry_delay=0.2) def __init__(self, base_url="http://127.0.0.1:8999", max_retries=5, retry_delay=0.2)
``` ```
### Connection Methods ### Connection Methods
@@ -400,21 +400,21 @@ Tool calls are executed concurrently within a single AI turn using `asyncio.gath
```python ```python
async def async_dispatch(tool_name: str, tool_input: dict[str, Any]) -> str: async def async_dispatch(tool_name: str, tool_input: dict[str, Any]) -> str:
""" """
Dispatch an MCP tool call by name asynchronously. Dispatch an MCP tool call by name asynchronously.
Returns the result as a string. Returns the result as a string.
""" """
# Run blocking I/O bound tools in a thread to allow parallel execution # Run blocking I/O bound tools in a thread to allow parallel execution
return await asyncio.to_thread(dispatch, tool_name, tool_input) return await asyncio.to_thread(dispatch, tool_name, tool_input)
``` ```
All tools are wrapped in `asyncio.to_thread()` to prevent blocking the event loop. This enables `ai_client.py` to execute multiple tools via `asyncio.gather()`: All tools are wrapped in `asyncio.to_thread()` to prevent blocking the event loop. This enables `ai_client.py` to execute multiple tools via `asyncio.gather()`:
```python ```python
results = await asyncio.gather( results = await asyncio.gather(
async_dispatch("read_file", {"path": "src/module_a.py"}), async_dispatch("read_file", {"path": "src/module_a.py"}),
async_dispatch("read_file", {"path": "src/module_b.py"}), async_dispatch("read_file", {"path": "src/module_b.py"}),
async_dispatch("get_file_summary", {"path": "src/module_c.py"}), async_dispatch("get_file_summary", {"path": "src/module_c.py"}),
) )
``` ```
@@ -453,13 +453,13 @@ Summary:
``` ```
logs/sessions/<session_id>/ logs/sessions/<session_id>/
comms.log # JSON-L: every API interaction (direction, kind, payload) comms.log # JSON-L: every API interaction (direction, kind, payload)
toolcalls.log # Markdown: sequential tool invocation records toolcalls.log # Markdown: sequential tool invocation records
apihooks.log # API hook invocations apihooks.log # API hook invocations
clicalls.log # JSON-L: CLI subprocess details (command, stdin, stdout, stderr, latency) clicalls.log # JSON-L: CLI subprocess details (command, stdin, stdout, stderr, latency)
scripts/generated/ scripts/generated/
<ts>_<seq:04d>.ps1 # Each AI-generated PowerShell script, preserved in order <ts>_<seq:04d>.ps1 # Each AI-generated PowerShell script, preserved in order
``` ```
### Logging Functions ### Logging Functions