From bd1d966c1297b2edcc84a07a11a9fdf5deb437b0 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 5 Jul 2026 20:16:53 -0400 Subject: [PATCH] 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). --- conductor/code_styleguides/error_handling.md | 358 ++++++------ conductor/product-guidelines.md | 38 +- conductor/product.md | 106 ++-- conductor/tech-stack.md | 34 +- conductor/workflow.md | 396 ++++++------- docs/Readme.md | 258 ++++----- docs/guide_ai_client.md | 364 ++++++------ docs/guide_architecture.md | 576 +++++++++---------- docs/guide_context_aggregation.md | 96 ++-- docs/guide_models.md | 10 +- docs/guide_simulations.md | 116 ++-- docs/guide_tools.md | 132 ++--- 12 files changed, 1242 insertions(+), 1242 deletions(-) diff --git a/conductor/code_styleguides/error_handling.md b/conductor/code_styleguides/error_handling.md index b47e62e2..341081fd 100644 --- a/conductor/code_styleguides/error_handling.md +++ b/conductor/code_styleguides/error_handling.md @@ -167,7 +167,7 @@ Need to represent "missing or failed"? | +-- Use raise (only for programmer errors like KeyError on a known dict) | +-- 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:** 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 - dataclass. + dataclass. 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 - (AND over OR). + (AND over OR). 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 - `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: - **`src/mcp_client.py:205-294`** — `read_file`, `list_directory`, - `search_files` return `Result[str]`; `(p, err)` tuples become - `Result[Path]`; the 30+ `assert p is not None` chain (lines 304-794) is - removed. + `search_files` return `Result[str]`; `(p, err)` tuples become + `Result[Path]`; the 30+ `assert p is not None` chain (lines 304-794) is + removed. - **`src/ai_client.py`** — `_send__result()` returns `Result[str]` - (8 vendors: gemini, anthropic, deepseek, minimax, gemini_cli, qwen, llama, - grok); `send(...) -> Result[str, ErrorInfo]` is the public API. + (8 vendors: gemini, anthropic, deepseek, minimax, qwen, llama, + grok); `send(...) -> Result[str, ErrorInfo]` is the public API. - **`src/rag_engine.py:100-180`** — `_init_vector_store_result`, - `_validate_collection_dim_result`, `is_empty_result`, `add_documents_result` - return `Result[None]` or `Result[T]`; broad `except Exception` blocks - become `ErrorInfo` entries. + `_validate_collection_dim_result`, `is_empty_result`, `add_documents_result` + return `Result[None]` or `Result[T]`; broad `except Exception` blocks + 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. - **`Optional[T]` return types are FORBIDDEN** in all `src/*.py`. Use - `Result[T]` (with `NIL_T` singleton if needed) instead. Rationale: - `Optional[T]` is the sum type `Union[T, None]` that Fleury's framework - replaces. Mixing the two patterns reintroduces the bifurcation the - convention is designed to remove. - - Argument types that may be `None` (e.g., `rag_engine: Optional[Any] = None`) - remain allowed; they describe a caller choice, not a runtime failure - of this function. Only `Optional[T]` *return* types are banned. + `Result[T]` (with `NIL_T` singleton if needed) instead. Rationale: + `Optional[T]` is the sum type `Union[T, None]` that Fleury's framework + replaces. Mixing the two patterns reintroduces the bifurcation the + convention is designed to remove. + - Argument types that may be `None` (e.g., `rag_engine: Optional[Any] = None`) + remain allowed; they describe a caller choice, not a runtime failure + of this function. Only `Optional[T]` *return* types are banned. - **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`) - doesn't need a `Result`. The classification is "can this return a different - value under different runtime conditions?" If yes, `Result`. If no, plain - return type. + 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 + value under different runtime conditions?" If yes, `Result`. If no, plain + return type. - **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 - (e.g., `_send__result()` wrapping the SDK call). Internal - `try/except` is reserved for converting `OSError`, `PermissionError`, and - similar I/O exceptions to `ErrorInfo` at the mcp_client tool boundary. + files, the only place an exception is caught is at the SDK call site + (e.g., `_send__result()` wrapping the SDK call). Internal + `try/except` is reserved for converting `OSError`, `PermissionError`, and + similar I/O exceptions to `ErrorInfo` at the mcp_client tool boundary. The verification script `scripts/audit_optional_returns.py` enforces the `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:** - 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 - (vs. just propagating `None`). + (vs. just propagating `None`). **Don't use it for:** - 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 - `Result`). + `Result`). - 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 async def _api_get_key(controller, header_key: str) -> str: 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 # Compliant: broad catch + HTTPException at the FastAPI boundary async def _api_generate(controller, payload): try: - result = ai_client.send(...) - return result.data + result = ai_client.send(...) + return result.data 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 @@ -353,13 +353,13 @@ HTTP status code is the framework contract. ### What is NOT a boundary - 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 - `app_controller.py` from a method in `app_controller.py` is internal, - not boundary. + `app_controller.py` from a method in `app_controller.py` is internal, + not boundary. - stdlib I/O that the user controls directly: opening a file the user - passed via `--config` is internal; converting the failure should be - Result-based, not exception-based. + passed via `--config` is internal; converting the failure should be + Result-based, not exception-based. --- @@ -391,10 +391,10 @@ where the caller of the drain point does NOT need to receive a ```python # COMPLIANT: drain point. The HTTP status code IS the error response. async def _api_get_track(controller, track_id: str) -> dict: - result = controller.get_track_result(track_id) - if not result.ok: - raise HTTPException(status_code=404, detail=result.errors[0].ui_message()) - return {"track": result.data} + result = controller.get_track_result(track_id) + if not result.ok: + raise HTTPException(status_code=404, detail=result.errors[0].ui_message()) + return {"track": result.data} ``` The caller (the HTTP client) receives an HTTP 4xx/5xx response. The @@ -407,10 +407,10 @@ the error. ```python # COMPLIANT: drain point. The user sees the error in the modal. def _show_track_load_failure(controller, track_id: str) -> None: - result = controller.get_track_result(track_id) - if not result.ok: - imgui.open_popup("Track Load Error") - # popup body reads result.errors[0].ui_message() and displays it + result = controller.get_track_result(track_id) + if not result.ok: + imgui.open_popup("Track Load Error") + # popup body reads result.errors[0].ui_message() and displays it ``` 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 # COMPLIANT: drain point. The app shuts down intentionally. def _shutdown_on_critical_failure(controller) -> None: - result = controller._init_session_db_result() - if not result.ok: - sys.stderr.write(f"FATAL: {result.errors[0].ui_message()}\n") - sys.exit(1) + result = controller._init_session_db_result() + if not result.ok: + sys.stderr.write(f"FATAL: {result.errors[0].ui_message()}\n") + sys.exit(1) ``` The error is propagated to the OS via `sys.exit(1)`. The drain point @@ -435,12 +435,12 @@ is the process termination itself. ```python # COMPLIANT: drain point. The error is sent to monitoring. def _report_failure_to_telemetry(controller, op_name: str, result: Result[T]) -> None: - if not result.ok: - telemetry.emit_error( - operation=op_name, - kind=result.errors[0].kind.value, - message=result.errors[0].message, - ) + if not result.ok: + telemetry.emit_error( + operation=op_name, + kind=result.errors[0].kind.value, + message=result.errors[0].message, +) ``` 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 # is reported back to the user (which is itself a drain point). def _load_track_with_retry(controller, track_id: str) -> Track | None: - for attempt in range(MAX_RETRIES): - result = controller.get_track_result(track_id) - if result.ok: - return result.data - time.sleep(BACKOFF_SECONDS * (attempt + 1)) - return None # Caller will display "failed after N attempts" + for attempt in range(MAX_RETRIES): + result = controller.get_track_result(track_id) + if result.ok: + return result.data + time.sleep(BACKOFF_SECONDS * (attempt + 1)) + return None # Caller will display "failed after N attempts" ``` 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: - **`sys.stderr.write(...)` alone** (without visible user feedback or - app-level decision): the data is lost; the user sees nothing. - Logging is NOT a drain. + app-level decision): the data is lost; the user sees nothing. + Logging is NOT a drain. - **`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 - 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. - **`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** 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: - **Boundary types** (Section: "Boundary Types") describe WHERE - exceptions originate or are converted (third-party SDK calls, stdlib - I/O, FastAPI handlers). The catch site at a boundary converts the - exception to `ErrorInfo` and returns it in `Result`. + exceptions originate or are converted (third-party SDK calls, stdlib + I/O, FastAPI handlers). The catch site at a boundary converts the + exception to `ErrorInfo` and returns it in `Result`. - **Drain points** describe WHERE the `Result[T]` propagation - terminates (HTTP error response, GUI display, app termination, - telemetry, bounded retry). The function at a drain point returns - `None` or raises into a framework; it does NOT return `Result[T]`. + terminates (HTTP error response, GUI display, app termination, + telemetry, bounded retry). The function at a drain point returns + `None` or raises into a framework; it does NOT return `Result[T]`. A function can be BOTH a boundary AND a drain point. The `_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: 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) 3. `except (SomeError): sys.exit(...)` (intentional termination) 4. `except (SomeError): telemetry.emit_*(...)` (telemetry) 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 note that the pattern is a drain point. @@ -554,18 +554,18 @@ calls): ```python def _validate_collection_dim_result(self) -> Result[None]: if self.collection is None or self.collection == "mock": - return Result(data=None) + return Result(data=None) try: - res = self.collection.get(limit=1, include=["embeddings"]) - # ... validation logic ... - return Result(data=None) + res = self.collection.get(limit=1, include=["embeddings"]) + # ... validation logic ... + return Result(data=None) except Exception as e: - return Result(data=None, errors=[ - ErrorInfo(kind=ErrorKind.INTERNAL, - message=f"Failed to validate collection dim: {e}", - source="rag._validate_collection_dim", - original=e) - ]) + return Result(data=None, errors=[ + ErrorInfo(kind=ErrorKind.INTERNAL, + message=f"Failed to validate collection dim: {e}", + source="rag._validate_collection_dim", + original=e) + ]) ``` This `except Exception` is **compliant** because the catch + ErrorInfo @@ -602,11 +602,11 @@ elaborates. ```python class MyClass: def __init__(self, config: Config): - if config is None: - raise ValueError("MyClass requires a non-None Config") - if not config.api_key: - raise ValueError("MyClass requires a non-empty api_key") - self._config = config + if config is None: + raise ValueError("MyClass requires a non-None Config") + if not config.api_key: + raise ValueError("MyClass requires a non-empty api_key") + self._config = config ``` **Compliant assert (for impossible states):** @@ -674,7 +674,7 @@ try: do_something(resource) finally: 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). @@ -759,15 +759,15 @@ script's output): Files scanned: 65 Files with findings: 42 Total sites: 348 -Compliant sites: 80 -Suspicious sites: 25 -Violation sites: 211 -Unclear (review): 32 +Compliant sites: 80 +Suspicious sites: 25 +Violation sites: 211 +Unclear (review): 32 --- Baseline (refactored files: mcp_client, ai_client, rag_engine) --- - Sites: 112, violations: 77 + Sites: 112, violations: 77 --- 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). @@ -788,15 +788,15 @@ When converting existing code: 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 - `errors: list[ErrorInfo]`. + `errors: list[ErrorInfo]`. 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 - `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` / - `try/except`. + `try/except`. 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 # BEFORE (the legacy): def do_thing() -> Optional[str]: - result = do_thing_result() - if not result.ok: return None - return result.data + result = do_thing_result() + if not result.ok: return None + return result.data # AFTER (the new): 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()`. @@ -832,7 +832,7 @@ The `do_thing` function must be **deleted**, not kept as a wrapper. Keep only on ```python # After OBLITERATE: only do_thing_result exists def do_thing_result() -> Result[str]: - ... + ... ``` Callers are rewritten: @@ -881,18 +881,18 @@ checklist before claiming a task is done.** **Before writing or modifying ANY `try/except` code, you MUST:** 1. **READ `conductor/code_styleguides/error_handling.md` end-to-end.** - The 7 sections are: (1) The 5 Patterns, (2) Decision Tree, - (3) Anti-Patterns, (4) Hard Rules, (5) Boundary Types, (6) The - Broad-Except Distinction, (7) AI Agent Checklist (this section). + The 7 sections are: (1) The 5 Patterns, (2) Decision Tree, + (3) Anti-Patterns, (4) Hard Rules, (5) Boundary Types, (6) The + Broad-Except Distinction, (7) AI Agent Checklist (this section). 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 + ." 3. **The styleguide is the source of truth.** Your training data is - the OPPOSITE of this convention. Idiomatic Python (`try/except` + - `Optional[T]` + `raise Exception`) is what the convention is - designed to REPLACE. + the OPPOSITE of this convention. Idiomatic Python (`try/except` + + `Optional[T]` + `raise Exception`) is what the convention is + designed to REPLACE. **Why:** the previous round (Phase 10) added 5 LAUNDERING HEURISTICS to 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: 1. **Use `Result[T]` for any function that can fail at runtime.** A - function that returns a different value under different runtime - conditions (success vs. failure) returns `Result[T]`, not - `Optional[T]`, not `T | None`, not a custom exception class. Use the - `Result` dataclass from `src/result_types.py`; populate - `errors: list[ErrorInfo]` on failure. + function that returns a different value under different runtime + conditions (success vs. failure) returns `Result[T]`, not + `Optional[T]`, not `T | None`, not a custom exception class. Use the + `Result` dataclass from `src/result_types.py`; populate + `errors: list[ErrorInfo]` on failure. 2. **Catch SDK exceptions at the boundary, convert to `ErrorInfo`.** If - your code calls `anthropic`, `google.genai`, `openai`, `chromadb`, - `requests`, or any other third-party SDK, the catch site - converts the exception to `ErrorInfo(kind=..., message=...)` and - returns it in `Result.errors`. Do NOT re-raise; do NOT swallow; - do NOT let the exception propagate into internal code. + your code calls `anthropic`, `google.genai`, `openai`, `chromadb`, + `requests`, or any other third-party SDK, the catch site + converts the exception to `ErrorInfo(kind=..., message=...)` and + returns it in `Result.errors`. Do NOT re-raise; do NOT swallow; + do NOT let the exception propagate into internal code. 3. **Use nil-sentinel dataclasses for "no result".** If a function - would return `None` in idiomatic Python, return a frozen - `NilPath` / `NilRAGState` / etc. singleton from - `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. + would return `None` in idiomatic Python, return a frozen + `NilPath` / `NilRAGState` / etc. singleton from + `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. 4. **Use `try/finally` (no except) for cleanup.** Bare - `try: ...; finally: cleanup()` is the canonical `goto defer` - pattern. Use it for resource cleanup, lock release, file handle - close. Do NOT use `try/except` + pass for cleanup; the cleanup - should run whether or not an exception occurred. + `try: ...; finally: cleanup()` is the canonical `goto defer` + pattern. Use it for resource cleanup, lock release, file handle + close. Do NOT use `try/except` + pass for cleanup; the cleanup + should run whether or not an exception occurred. 5. **`raise` is reserved for programmer errors.** `assert` for - "this should never happen" invariants. `raise ValueError`, - `raise NotImplementedError`, `raise KeyError` in `__init__` for - "this object needs X." Do NOT use `raise` for runtime failures - (the network is down, the file doesn't exist, the API rate-limited); - those are `Result` cases. + "this should never happen" invariants. `raise ValueError`, + `raise NotImplementedError`, `raise KeyError` in `__init__` for + "this object needs X." Do NOT use `raise` for runtime failures + (the network is down, the file doesn't exist, the API rate-limited); + those are `Result` cases. ### The 7 MUST-NOT-DO rules When writing NEW code, you MUST NOT: 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 - `Optional[T]` return type to any `src/*.py` (enforced by - `scripts/audit_optional_in_baseline_files.py --strict`, - which scans all `src/*.py` as of 2026-06-27). + `src/`). Use `Result[T]` instead. CI fails if you add a new + `Optional[T]` return type to any `src/*.py` (enforced by + `scripts/audit_optional_in_baseline_files.py --strict`, + which scans all `src/*.py` as of 2026-06-27). 2. **DO NOT use `Optional[T]` as a return type** (anywhere else in - `src/`). The convention is migrating to `Result[T]`; new code - should set the pattern, not perpetuate the old one. Argument - types that may be `None` (caller choice) are still OK. + `src/`). The convention is migrating to `Result[T]`; new code + should set the pattern, not perpetuate the old one. Argument + types that may be `None` (caller choice) are still OK. 3. **DO NOT use `None` as a sentinel for "no result".** Use a - nil-sentinel dataclass. The data is zero-initialized; the caller - doesn't need a None check. + nil-sentinel dataclass. The data is zero-initialized; the caller + doesn't need a None check. 4. **DO NOT raise a custom exception class for runtime failures.** - SDK exceptions caught and converted to `ErrorInfo` is the only - legitimate exception path. Internal code uses `Result`. + SDK exceptions caught and converted to `ErrorInfo` is the only + legitimate exception path. Internal code uses `Result`. 5. **DO NOT use `Union[T, E]` (sum type).** Use `Result[T]` with - side-channel `errors: list[ErrorInfo]`. The result is the data - AND the errors, not a tagged sum. + side-channel `errors: list[ErrorInfo]`. The result is the data + AND the errors, not a tagged sum. 6. **DO NOT catch `except Exception` and silently swallow.** Either - narrow the exception type, convert to `ErrorInfo` in a `Result`, - or document the intentional swallow with a comment-free `assert` - for the precondition. The audit script flags this as - `INTERNAL_SILENT_SWALLOW`. + narrow the exception type, convert to `ErrorInfo` in a `Result`, + or document the intentional swallow with a comment-free `assert` + for the precondition. The audit script flags this as + `INTERNAL_SILENT_SWALLOW`. 7. **DO NOT catch `except Exception` in non-`*_result` code without - conversion to `ErrorInfo`.** If you must catch, convert: - `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`. + conversion to `ErrorInfo`.** If you must catch, convert: + `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 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. 1. **Third-party SDK calls.** Wrapping `anthropic.Anthropic().messages.create(...)` - in `try/except anthropic.APIError` is the canonical pattern. - Convert to `ErrorInfo`; return in `Result`. + in `try/except anthropic.APIError` is the canonical pattern. + Convert to `ErrorInfo`; return in `Result`. 2. **Stdlib I/O that can raise.** `open()`, `os.path.*`, - `json.loads()`, `subprocess.run()`, `socket.*`, `sqlite3.*`, - `chromadb.PersistentClient()` can all raise. Catch the specific - exception (`OSError`, `FileNotFoundError`, `json.JSONDecodeError`, - `subprocess.CalledProcessError`, etc.); convert to `ErrorInfo`. + `json.loads()`, `subprocess.run()`, `socket.*`, `sqlite3.*`, + `chromadb.PersistentClient()` can all raise. Catch the specific + exception (`OSError`, `FileNotFoundError`, `json.JSONDecodeError`, + `subprocess.CalledProcessError`, etc.); convert to `ErrorInfo`. 3. **FastAPI `HTTPException` in `_api_*` handlers.** `raise - HTTPException(status_code=..., detail=...)` in a function named - `_api_*` is the FastAPI-idiomatic way to signal HTTP errors. - FastAPI converts it to a JSON response at the framework level. - This is NOT an exception leak; it's the framework contract. + HTTPException(status_code=..., detail=...)` in a function named + `_api_*` is the FastAPI-idiomatic way to signal HTTP errors. + FastAPI converts it to a JSON response at the framework level. + This is NOT an exception leak; it's the framework contract. ### 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 - that established this convention. + that established this convention. - `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)" - — 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/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 - 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) - — the philosophical foundation. + — the philosophical foundation. diff --git a/conductor/product-guidelines.md b/conductor/product-guidelines.md index c717e49f..b7164cec 100644 --- a/conductor/product-guidelines.md +++ b/conductor/product-guidelines.md @@ -28,12 +28,12 @@ The canonical mandate is in `conductor/code_styleguides/data_oriented_design.md` ## Code Standards & Architecture - **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. - - 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'). - - 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. - - Utilize proper asynchronous batching and queue-based pipelines for background AI work, ensuring a data-oriented flow rather than tangled object-oriented state graphs. + - **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. + - 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. + - 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. - **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. - **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. - **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. - - **Functions/Methods:** `[C: Caller1, Caller2]` (Primary callers). - - **State Variables:** `[M: File:Line, Method]` (Mutation points) and `[U: File]` (Major use paths). + - **Functions/Methods:** `[C: Caller1, Caller2]` (Primary callers). + - **State Variables:** `[M: File:Line, Method]` (Mutation points) and `[U: File]` (Major use paths). ## 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: 1. **`conductor/code_styleguides/error_handling.md`** — the canonical - styleguide. Has 5 patterns, 3 boundary types, 1 broad-except - distinction rule, 1 constructor-raise rule, 1 re-raise rule, and - the audit script reference. Read this before writing any code that - can fail at runtime. + styleguide. Has 5 patterns, 3 boundary types, 1 broad-except + distinction rule, 1 constructor-raise rule, 1 re-raise rule, and + the audit script reference. Read this before writing any code that + can fail at runtime. 2. **`conductor/code_styleguides/error_handling.md` "AI Agent Checklist"** — - 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 - done. + 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 + done. 3. **`scripts/audit_exception_handling.py`** — the static analyzer - that catches violations before commit. The script classifies - `try/except/finally/raise` sites against 10 categories. Use it - pre-commit. + that catches violations before commit. The script classifies + `try/except/finally/raise` sites against 10 categories. Use it + pre-commit. 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):** diff --git a/conductor/product.md b/conductor/product.md index 1df3fed1..b7454610 100644 --- a/conductor/product.md +++ b/conductor/product.md @@ -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): **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_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 @@ -17,7 +17,7 @@ For deep implementation details when planning or implementing tracks, consult `d **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_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_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 @@ -49,57 +49,57 @@ For deep implementation details when planning or implementing tracks, consult `d ## 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. - - **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 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. - - **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. - - **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. - - **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. - - **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). + - **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 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. + - **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. + - **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. + - **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. + - **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). - - **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. - - **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. + - **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. + - **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. - **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. - **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. - - **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. - - **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. + - **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. + - **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. - **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. - **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). - - **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. - - **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. + - **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). + - **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. + - **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. - - **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. - - **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. - - **Automated Synchronization:** Features background re-indexing of the project workspace, ensuring the vector store remains consistent with the current project state. + - **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. + - **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. + - **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. - - **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. - - **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. + - **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. + - **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. - **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. - **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. - - **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. + - **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. - **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. - **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). - **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. -- **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). - - **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. - - **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. - - **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. - - **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. - - **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. - - **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. - - **Fine-Grained Weight Control:** Integrated sliders in the Preset Manager for adjusting individual tool weights (1-5) and parameter-level biases. + - **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. + - **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. + - **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. + - **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. + - **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. + - **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. + - **Fine-Grained Weight Control:** Integrated sliders in the Preset Manager for adjusting individual tool weights (1-5) and parameter-level biases. diff --git a/conductor/tech-stack.md b/conductor/tech-stack.md index 4f555bb6..9aac53b2 100644 --- a/conductor/tech-stack.md +++ b/conductor/tech-stack.md @@ -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. @@ -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/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). - - **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. - - **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`. - - **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. + - **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. + - **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 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). + - **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). - - **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. - - **`/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. + - **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. + - **`/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. - **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. - **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. - - **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. - - **defensive try/except wrapping:** All action callbacks wrapped to prevent GUI crashes from buggy commands. + - **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). + - **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. ## Per-Source-File Deep Dives 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_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_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) diff --git a/conductor/workflow.md b/conductor/workflow.md index a2795a21..56446403 100644 --- a/conductor/workflow.md +++ b/conductor/workflow.md @@ -42,10 +42,10 @@ Or use Python subprocess with `newline=''` to preserve line endings: ```python python -c " with open('file.py', 'r', encoding='utf-8', newline='') as f: - content = f.read() + content = f.read() content = content.replace(old, new) 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/.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/.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. 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_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_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_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_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_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_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. + - **[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_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_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_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_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_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`. + - 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 @@ -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 `[~]` 3. **High-Signal Research Phase:** - - **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. - - **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. - - **Minimize Token Burn:** Only use `read_file` with `start_line`/`end_line` for specific implementation details once target areas are identified. + - **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. + - **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. + - **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):** - - **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. - - **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. - - 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. + - **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. + - **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. + - 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. 5. **Implement to Pass Tests (Green Phase):** - - **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. - - **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. - - Run the test suite again and confirm that all tests now pass. This is the "Green" phase. + - **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. + - **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. + - Run the test suite again and confirm that all tests now pass. This is the "Green" phase. 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. - - Rerun tests to ensure they still pass after refactoring. + - 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. 7. **Verify Coverage:** Run coverage reports using the project's chosen tools. For example, in a Python project, this might look like: - ```powershell - pytest --cov=app --cov-report=html - ``` - Target: >80% coverage for new code. The specific tools and commands will vary by language and framework. + ```powershell + pytest --cov=app --cov-report=html + ``` + 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: - - **STOP** implementation - - Update `tech-stack.md` with new design - - Add dated note explaining the change - - Resume implementation + - **STOP** implementation + - Update `tech-stack.md` with new design + - Add dated note explaining the change + - Resume implementation 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. - - Stage all code changes related to the task. - - Propose a clear, concise commit message e.g, `feat(ui): Create basic HTML structure for calculator`. - - Perform the commit. + - **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. + - Propose a clear, concise commit message e.g, `feat(ui): Create basic HTML structure for calculator`. + - Perform the commit. 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.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. - ```powershell - # The note content from the previous step is passed via the -m flag. - git notes add -m "" - ``` + - **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.3: Attach Note:** Use the `git notes` command to attach the summary to the commit. + ```powershell + # The note content from the previous step is passed via the -m flag. + git notes add -m "" + ``` 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.2: Write Plan:** Write the updated content back to `plan.md`. + - **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`. 12. **Commit Plan Update:** - - **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:** 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`). ### 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`. -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:** - - **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 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: - - **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. - - 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`). +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.2: List Changed Files:** Execute `git diff --name-only 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: + - **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. + - 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:** - - 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. - - **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`" - - 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.) - - 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. +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. + - 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). + - **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. + - 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. -4. **Execute Automated API Hook Verification:** - - **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. - - It will then communicate with the application's IPC server to trigger the necessary verification functions. - - **Result Handling:** - - 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 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. +4. **Execute Automated API Hook Verification:** + - **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. + - It will then communicate with the application's IPC server to trigger the necessary verification functions. + - **Result Handling:** + - 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 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:** - - 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 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. +5. **Present Automated Verification Results and User Confirmation:** + - 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 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. -6. **Create Checkpoint 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`). +6. **Create Checkpoint 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`). -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.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. +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.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:** - - **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: ]`. - - **Step 8.3: Write Plan:** Write the updated content back to `plan.md`. +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.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: ]`. + - **Step 8.3: Write Plan:** Write the updated content back to `plan.md`. 9. **Commit Plan Update:** - - **Action:** Stage the modified `plan.md` file. - - **Action:** Commit this change with a descriptive message following the format `conductor(plan): Mark phase '' as complete`. + - **Action:** Stage the modified `plan.md` file. + - **Action:** Commit this change with a descriptive message following the format `conductor(plan): Mark phase '' 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 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: - ```powershell - uv run python gui.py --enable-test-hooks - ``` - This starts the hook server on port `8999`. +1. **Launch the App with Hooks:** Run the application in a separate shell with the `--enable-test-hooks` flag: + ```powershell + uv run python gui.py --enable-test-hooks + ``` + 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. - ```python - def test_my_feature(live_gui): - # The GUI is now running on port 8999 - ... - ``` - Note: pytest must be run with `uv`. +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 + def test_my_feature(live_gui): + # The GUI is now running on port 8999 + ... + ``` + 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: - ```powershell - Invoke-RestMethod -Uri "http://127.0.0.1:8999/status" -Method Get - ``` +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 + Invoke-RestMethod -Uri "http://127.0.0.1:8999/status" -Method Get + ``` ### Quality Gates @@ -275,9 +275,9 @@ Before marking any task complete, verify: ### 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. -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. +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. +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 @@ -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. - **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:** - - **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 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 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 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. - **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 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 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 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. - **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 # WRONG: race condition def test_open_modal(live_gui): - client.push_event("custom_callback", {"callback": "_toggle_settings", "args": []}) - time.sleep(1) # hope the modal opened - assert some_cached_value["settings_open"] is True # may be stale + client.push_event("custom_callback", {"callback": "_toggle_settings", "args": []}) + time.sleep(1) # hope the modal opened + assert some_cached_value["settings_open"] is True # may be stale # RIGHT: poll-until-state-visible def test_open_modal(live_gui): - client.push_event("custom_callback", {"callback": "_toggle_settings", "args": []}) - assert client.get_value("show_settings_modal"), "settings modal did not open" + client.push_event("custom_callback", {"callback": "_toggle_settings", "args": []}) + 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). @@ -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:** - After modifying a class body, walk the AST and verify all expected methods are class-level: - ```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)]" - ``` + ```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)]" + ``` - 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. @@ -695,42 +695,42 @@ Audit, Goals, Non-Goals, Architecture, Risks, Verification, etc.) with these specific Tier 1 rules: - **Current State Audit is MANDATORY** before writing requirements. Read - the actual code with MCP tools (`get_file_slice`, `py_get_skeleton`, - `py_get_definition`, `py_find_usages`). Document existing - implementations with `file:line` references in a "Current State - Audit" section. Failure to audit = track failure. + the actual code with MCP tools (`get_file_slice`, `py_get_skeleton`, + `py_get_definition`, `py_find_usages`). Document existing + implementations with `file:line` references in a "Current State + Audit" section. Failure to audit = track failure. - **Frame requirements as GAPS, not features.** "The existing X - (file.py:L100-200) has Y; this track fills the gap" — not "Build - feature Z". + (file.py:L100-200) has Y; this track fills the gap" — not "Build + feature Z". - **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 - plan; the Tier 1 writes it for a Tier 3 to execute. + 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. - **Reference architecture docs** (`docs/guide_*.md`, - `conductor/code_styleguides/*.md`) in every spec. Every requirement - must point to the existing pattern it follows (or the new pattern it - establishes). + `conductor/code_styleguides/*.md`) in every spec. Every requirement + must point to the existing pattern it follows (or the new pattern it + establishes). - **For bug fix tracks: Root Cause Analysis** is mandatory. Read the - 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 - report". + 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 + report". ### 3. Metadata format The `metadata.json` follows the standard schema. Specific Tier 1 rules: - `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. - `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 - title + description + track_status. No "estimated effort". + title + description + track_status. No "estimated effort". - `estimated_effort` field uses `method: "scope (per workflow.md §Tier - 1 Track Initialization Rules). NO day estimates."` and a per-phase - `scope` summary (e.g., `phase_1: "1 task: investigation"`). + 1 Track Initialization Rules). NO day estimates."` and a per-phase + `scope` summary (e.g., `phase_1: "1 task: investigation"`). - `risk_register` entries use scope-relative likelihood ("medium" - means "the implementation may be larger than the spec suggests"), - not time-relative ("takes longer than 2 days"). + means "the implementation may be larger than the spec suggests"), + not time-relative ("takes longer than 2 days"). ### 4. Plan format @@ -738,11 +738,11 @@ The `plan.md` follows the standard TDD red-first template. Specific Tier 1 rules: - 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: - 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 - themselves. + themselves. ### 5. The "Reasonable effort" guard @@ -770,8 +770,8 @@ Every track's `conductor/tracks//state.toml` should follow this struct [meta] track_id = "" name = "" -status = "active" # active | completed -current_phase = 0 # 0 = pre-Phase 1; 1..N = in Phase N; "complete" if all phases done +status = "active" # active | completed +current_phase = 0 # 0 = pre-Phase 1; 1..N = in Phase N; "complete" if all phases done last_updated = "" [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. 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 - - Add a clarifying task to the plan - - Defer the work to a follow-up track + - Update the spec to match the new constraint + - Add a clarifying task to the plan + - 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. **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 - - [ ] parse_harvest_json: 7 categories; rows must be lists - - [ ] parse_harvest_json: rejects prose - - [ ] parse_harvest_json: tolerates ```json ... ``` code-fence - - [ ] parse_harvest_json: rejects non-dict payloads - - [ ] regenerate_digest: 4KB cap; truncation with note + - [ ] parse_harvest_json: 7 categories; rows must be lists + - [ ] parse_harvest_json: rejects prose + - [ ] parse_harvest_json: tolerates ```json ... ``` code-fence + - [ ] parse_harvest_json: rejects non-dict payloads + - [ ] regenerate_digest: 4KB cap; truncation with note - [ ] tests/test_knowledge_harvest.py: 8+ tests for the pipeline - - [ ] classify (live/user-kept/prune/harvest/keep) - - [ ] merge_harvest per category - - [ ] per-file knowledge: existing-file branch - - [ ] per-file knowledge: missing-file branch - - [ ] ledger dedup (sha256-of-content) - - [ ] retry budget (2 attempts) - - [ ] "too-large" budget guard (1MB) - - [ ] "delete to turn off" regeneration + - [ ] classify (live/user-kept/prune/harvest/keep) + - [ ] merge_harvest per category + - [ ] per-file knowledge: existing-file branch + - [ ] per-file knowledge: missing-file branch + - [ ] ledger dedup (sha256-of-content) + - [ ] retry budget (2 attempts) + - [ ] "too-large" budget guard (1MB) + - [ ] "delete to turn off" regeneration ``` ### 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 - - [ ] first N chars are identical across turns of the same discussion - - [ ] N = aggregate.stable_prefix_length(ctrl) - - [ ] failure modes: new layer in wrong position, volatile input leak + - [ ] first N chars are identical across turns of the same discussion + - [ ] N = aggregate.stable_prefix_length(ctrl) + - [ ] failure modes: new layer in wrong position, volatile input leak - [ ] tests/test_cache_state.py: 3+ tests for the cache state machine - - [ ] per-provider TTL defaults - - [ ] DiscussionCacheState lifecycle - - [ ] invalidate + regeneration + - [ ] per-provider TTL defaults + - [ ] DiscussionCacheState lifecycle + - [ ] invalidate + regeneration - [ ] tests/test_gui_caching.py: 3+ live_gui tests for the "Caching" panel - - [ ] panel renders provider summaries - - [ ] invalidate button - - [ ] per-discussion disable/enable + - [ ] panel renders provider summaries + - [ ] invalidate button + - [ ] per-discussion disable/enable ``` ### 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 - - [ ] compact preserves decisions - - [ ] compact preserves constraints - - [ ] compact preserves failures - - [ ] compact preserves artifact refs - - [ ] compact removes duplicates - - [ ] compact replaces chronology with state - - [ ] compact is substantially smaller - - [ ] compact preserves capability - - [ ] compact returns 12-section structure - - [ ] compact continues until self-review passes + - [ ] compact preserves decisions + - [ ] compact preserves constraints + - [ ] compact preserves failures + - [ ] compact preserves artifact refs + - [ ] compact removes duplicates + - [ ] compact replaces chronology with state + - [ ] compact is substantially smaller + - [ ] compact preserves capability + - [ ] compact returns 12-section structure + - [ ] compact continues until self-review passes ``` ### 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 - - [ ] RAG disabled: no {rag-context} block - - [ ] RAG results have provenance (file path + chunk) - - [ ] RAG results do not mutate disc_entries - - [ ] RAG failure returns empty (graceful) + - [ ] RAG disabled: no {rag-context} block + - [ ] RAG results have provenance (file path + chunk) + - [ ] RAG results do not mutate disc_entries + - [ ] RAG failure returns empty (graceful) ``` See `conductor/code_styleguides/knowledge_artifacts.md`, `cache_friendly_context.md`, `rag_integration_discipline.md` for the canonical styleguides. diff --git a/docs/Readme.md b/docs/Readme.md index 76fe23a1..70364bf4 100644 --- a/docs/Readme.md +++ b/docs/Readme.md @@ -14,7 +14,7 @@ This documentation suite provides comprehensive technical reference for the Manu | 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. | | [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) | @@ -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) | | [Themes](guide_themes.md) | TOML-based theming system: file layout (`themes/.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) | -| [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 | | [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 | | [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 (`_take_`), 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 | | [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. - **Paths**: Explicit files or wildcard globs (`src/**/*.py`). - **File Flags**: - - **Auto-Aggregate**: Include in context compilation. - - **Force Full**: Bypass summary-only mode for this file. + - **Auto-Aggregate**: Include in context compilation. + - **Force Full**: Bypass summary-only mode for this file. - **Cache Indicator**: Green dot (●) indicates file is in provider's context cache. ### Discussion Hub @@ -101,7 +101,7 @@ Manages conversational branches to prevent context poisoning across tasks. ### 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. - **Fetch Models**: Queries the active provider for the latest model list. - **Temperature / Max Tokens**: Generation parameters. @@ -324,127 +324,127 @@ EXPANDED = "${HOME}/subdir" ``` manual_slop/ -├── conductor/ # Conductor system -│ ├── tracks/ # Track directories -│ │ └── / # Per-track files -│ │ ├── spec.md -│ │ ├── plan.md -│ │ ├── metadata.json -│ │ └── state.toml -│ ├── archive/ # Completed tracks -│ ├── product.md # Product definition -│ ├── product-guidelines.md -│ ├── tech-stack.md -│ ├── workflow.md -│ ├── index.md -│ └── edit_workflow.md -├── docs/ # Deep-dive documentation (27 guides + specs/plans) -│ ├── guide_ai_client.md # Multi-provider LLM client -│ ├── guide_api_hooks.md # HookServer + ApiHookClient -│ ├── guide_app_controller.md # Headless AppController -│ ├── guide_architecture.md # Threading, event system, state machines -│ ├── guide_beads.md # Beads/Dolt issue tracking -│ ├── guide_command_palette.md # Command palette + 33 registered commands -│ ├── guide_context_aggregation.md # aggregate.py pipeline (strategies + view modes) -│ ├── guide_context_curation.md # Granular AST control + Fuzzy Anchor slices -│ ├── guide_discussions.md # Discussion system + A1-A7 matrix -│ ├── guide_docker_deployment.md # Docker + Gitea registry deployment -│ ├── guide_gui_2.md # Main ImGui interface (App class, render functions) -│ ├── guide_hot_reload.md # State-preserving module reloading -│ ├── guide_mcp_client.md # 45 MCP tools + 3-layer security -│ ├── guide_meta_boundary.md # Application vs Meta-Tooling split -│ ├── guide_mma.md # 4-Tier MMA concepts -│ ├── guide_models.md # Data model registry -│ ├── guide_multi_agent_conductor.md # ConductorEngine + TrackDAG + WorkerPool -│ ├── guide_nerv_theme.md # NERV Tactical Console theme -│ ├── guide_personas.md # Unified agent profile system -│ ├── guide_rag.md # RAG subsystem (ChromaDB + embeddings) -│ ├── guide_shaders_and_window.md # Shader injection + custom window frame -│ ├── guide_simulations.md # Test framework + Puppeteer pattern -│ ├── guide_state_lifecycle.md # Undo/redo + state delegation -│ ├── guide_testing.md # 322 test files + 7 conftest fixtures -│ ├── guide_themes.md # Multi-theme TOML system -│ ├── guide_tools.md # MCP tools + shell runner -│ ├── guide_workspace_profiles.md # Workspace profile save/load -│ ├── Readme.md -│ ├── MMA_Support/ # Legacy MMA reference (deprecated) -│ ├── reports/ # Phase 5 reports -│ └── superpowers/ # Specs and plans for design work -├── src/ # Core implementation (53 modules) -│ ├── gui_2.py # Primary ImGui interface -│ ├── app_controller.py # Headless controller -│ ├── 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 -│ ├── api_hooks.py # HookServer REST API on :8999 -│ ├── api_hook_client.py # Python client for the Hook API -│ ├── multi_agent_conductor.py # ConductorEngine -│ ├── dag_engine.py # TrackDAG + ExecutionEngine -│ ├── models.py # Ticket, Track, WorkerContext, etc. -│ ├── events.py # EventEmitter, SyncEventQueue -│ ├── project_manager.py # TOML persistence, discussion management -│ ├── session_logger.py # JSON-L + markdown audit trails -│ ├── rag_engine.py # RAG (ChromaDB + embedding providers) -│ ├── beads_client.py # Beads/Dolt issue tracking client -│ ├── hot_reloader.py # State-preserving module reloader -│ ├── personas.py # Unified agent profile manager -│ ├── presets.py # System prompt preset manager -│ ├── context_presets.py # Context composition preset manager -│ ├── tool_presets.py # Tool preset manager -│ ├── tool_bias.py # Tool bias engine -│ ├── command_palette.py # Command palette + fuzzy matcher -│ ├── commands.py # 33 registered commands -│ ├── workspace_manager.py # Workspace profile save/load -│ ├── theme_2.py # Theme system (palette/font/etc.) -│ ├── theme_nerv.py # NERV Tactical Console theme -│ ├── theme_nerv_fx.py # NERV FX (scanlines, flicker, alert) -│ ├── shell_runner.py # PowerShell execution with 60s timeout + qa_callback + patch_callback -│ ├── file_cache.py # ASTParser (tree-sitter) -│ ├── summarize.py # Heuristic file summaries -│ ├── outline_tool.py # Hierarchical code outline -│ ├── fuzzy_anchor.py # Fuzzy anchor slice algorithm -│ ├── history.py # Undo/redo HistoryManager -│ ├── imgui_scopes.py # ImGui context managers -│ ├── performance_monitor.py # FPS/CPU tracking -│ ├── log_registry.py # Session metadata -│ ├── log_pruner.py # Automated log cleanup -│ ├── paths.py # Centralized path resolution -│ ├── cost_tracker.py # Token cost estimation -│ ├── gemini_cli_adapter.py # CLI subprocess adapter -│ ├── mma_prompts.py # Tier-specific system prompts -│ ├── summary_cache.py # SHA256-keyed summary LRU cache -│ ├── markdown_helper.py # Markdown rendering helpers -│ ├── patch_modal.py # Patch approval modal -│ ├── diff_viewer.py # Diff rendering -│ ├── external_editor.py # External editor integration -│ ├── orchestrator_pm.py # Orchestrator project manager -│ ├── conductor_tech_lead.py # Tier 2 ticket generation -│ ├── synthesis_formatter.py # Multi-take synthesis -│ ├── thinking_parser.py # AI thinking-trace extraction -│ └── __init__.py -├── simulation/ # Test simulations -│ ├── sim_base.py # BaseSimulation class -│ ├── workflow_sim.py # WorkflowSimulator -│ ├── user_agent.py # UserSimAgent -│ ├── sim_context.py # ContextSimulation -│ ├── sim_execution.py # ExecutionSimulation -│ ├── sim_ai_settings.py # AISettingsSimulation -│ └── sim_tools.py # ToolsSimulation -├── tests/ # Test suite (251 files) -│ ├── conftest.py # Fixtures (live_gui, isolate_workspace, etc.) -│ ├── mock_gemini_cli.py # Mock provider for integration tests -│ ├── test_*.py # Unit tests -│ ├── *_sim.py # Integration tests using live_gui -│ ├── test_clean_install.py # Opt-in: clones repo and verifies hooks -│ ├── test_docker_build.py # Opt-in: builds Docker image -│ ├── artifacts/ # Git-ignored; test outputs -│ └── logs/ # Git-ignored; live_gui log files -├── scripts/ # Utility scripts -│ ├── generated/ # AI-generated scripts -│ ├── check_test_toml_paths.py # Audit script (CI gate) -│ ├── docker_build.sh -│ └── docker_run.sh -├── sloppy.py # Main entry point -├── config.toml # Global configuration -├── manual_slop.toml # Active project config (current) -└── credentials.toml # API keys (gitignored) +├── conductor/ # Conductor system +│ ├── tracks/ # Track directories +│ │ └── / # Per-track files +│ │ ├── spec.md +│ │ ├── plan.md +│ │ ├── metadata.json +│ │ └── state.toml +│ ├── archive/ # Completed tracks +│ ├── product.md # Product definition +│ ├── product-guidelines.md +│ ├── tech-stack.md +│ ├── workflow.md +│ ├── index.md +│ └── edit_workflow.md +├── docs/ # Deep-dive documentation (27 guides + specs/plans) +│ ├── guide_ai_client.md # Multi-provider LLM client +│ ├── guide_api_hooks.md # HookServer + ApiHookClient +│ ├── guide_app_controller.md # Headless AppController +│ ├── guide_architecture.md # Threading, event system, state machines +│ ├── guide_beads.md # Beads/Dolt issue tracking +│ ├── guide_command_palette.md # Command palette + 33 registered commands +│ ├── guide_context_aggregation.md # aggregate.py pipeline (strategies + view modes) +│ ├── guide_context_curation.md # Granular AST control + Fuzzy Anchor slices +│ ├── guide_discussions.md # Discussion system + A1-A7 matrix +│ ├── guide_docker_deployment.md # Docker + Gitea registry deployment +│ ├── guide_gui_2.md # Main ImGui interface (App class, render functions) +│ ├── guide_hot_reload.md # State-preserving module reloading +│ ├── guide_mcp_client.md # 45 MCP tools + 3-layer security +│ ├── guide_meta_boundary.md # Application vs Meta-Tooling split +│ ├── guide_mma.md # 4-Tier MMA concepts +│ ├── guide_models.md # Data model registry +│ ├── guide_multi_agent_conductor.md # ConductorEngine + TrackDAG + WorkerPool +│ ├── guide_nerv_theme.md # NERV Tactical Console theme +│ ├── guide_personas.md # Unified agent profile system +│ ├── guide_rag.md # RAG subsystem (ChromaDB + embeddings) +│ ├── guide_shaders_and_window.md # Shader injection + custom window frame +│ ├── guide_simulations.md # Test framework + Puppeteer pattern +│ ├── guide_state_lifecycle.md # Undo/redo + state delegation +│ ├── guide_testing.md # 322 test files + 7 conftest fixtures +│ ├── guide_themes.md # Multi-theme TOML system +│ ├── guide_tools.md # MCP tools + shell runner +│ ├── guide_workspace_profiles.md # Workspace profile save/load +│ ├── Readme.md +│ ├── MMA_Support/ # Legacy MMA reference (deprecated) +│ ├── reports/ # Phase 5 reports +│ └── superpowers/ # Specs and plans for design work +├── src/ # Core implementation (53 modules) +│ ├── gui_2.py # Primary ImGui interface +│ ├── app_controller.py # Headless controller +│ ├── 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 +│ ├── api_hooks.py # HookServer REST API on :8999 +│ ├── api_hook_client.py # Python client for the Hook API +│ ├── multi_agent_conductor.py # ConductorEngine +│ ├── dag_engine.py # TrackDAG + ExecutionEngine +│ ├── models.py # Ticket, Track, WorkerContext, etc. +│ ├── events.py # EventEmitter, SyncEventQueue +│ ├── project_manager.py # TOML persistence, discussion management +│ ├── session_logger.py # JSON-L + markdown audit trails +│ ├── rag_engine.py # RAG (ChromaDB + embedding providers) +│ ├── beads_client.py # Beads/Dolt issue tracking client +│ ├── hot_reloader.py # State-preserving module reloader +│ ├── personas.py # Unified agent profile manager +│ ├── presets.py # System prompt preset manager +│ ├── context_presets.py # Context composition preset manager +│ ├── tool_presets.py # Tool preset manager +│ ├── tool_bias.py # Tool bias engine +│ ├── command_palette.py # Command palette + fuzzy matcher +│ ├── commands.py # 33 registered commands +│ ├── workspace_manager.py # Workspace profile save/load +│ ├── theme_2.py # Theme system (palette/font/etc.) +│ ├── theme_nerv.py # NERV Tactical Console theme +│ ├── theme_nerv_fx.py # NERV FX (scanlines, flicker, alert) +│ ├── shell_runner.py # PowerShell execution with 60s timeout + qa_callback + patch_callback +│ ├── file_cache.py # ASTParser (tree-sitter) +│ ├── summarize.py # Heuristic file summaries +│ ├── outline_tool.py # Hierarchical code outline +│ ├── fuzzy_anchor.py # Fuzzy anchor slice algorithm +│ ├── history.py # Undo/redo HistoryManager +│ ├── imgui_scopes.py # ImGui context managers +│ ├── performance_monitor.py # FPS/CPU tracking +│ ├── log_registry.py # Session metadata +│ ├── log_pruner.py # Automated log cleanup +│ ├── paths.py # Centralized path resolution +│ ├── cost_tracker.py # Token cost estimation +│ ├── gemini_cli_adapter.py # CLI subprocess adapter +│ ├── mma_prompts.py # Tier-specific system prompts +│ ├── summary_cache.py # SHA256-keyed summary LRU cache +│ ├── markdown_helper.py # Markdown rendering helpers +│ ├── patch_modal.py # Patch approval modal +│ ├── diff_viewer.py # Diff rendering +│ ├── external_editor.py # External editor integration +│ ├── orchestrator_pm.py # Orchestrator project manager +│ ├── conductor_tech_lead.py # Tier 2 ticket generation +│ ├── synthesis_formatter.py # Multi-take synthesis +│ ├── thinking_parser.py # AI thinking-trace extraction +│ └── __init__.py +├── simulation/ # Test simulations +│ ├── sim_base.py # BaseSimulation class +│ ├── workflow_sim.py # WorkflowSimulator +│ ├── user_agent.py # UserSimAgent +│ ├── sim_context.py # ContextSimulation +│ ├── sim_execution.py # ExecutionSimulation +│ ├── sim_ai_settings.py # AISettingsSimulation +│ └── sim_tools.py # ToolsSimulation +├── tests/ # Test suite (251 files) +│ ├── conftest.py # Fixtures (live_gui, isolate_workspace, etc.) +│ ├── mock_gemini_cli.py # Mock provider for integration tests +│ ├── test_*.py # Unit tests +│ ├── *_sim.py # Integration tests using live_gui +│ ├── test_clean_install.py # Opt-in: clones repo and verifies hooks +│ ├── test_docker_build.py # Opt-in: builds Docker image +│ ├── artifacts/ # Git-ignored; test outputs +│ └── logs/ # Git-ignored; live_gui log files +├── scripts/ # Utility scripts +│ ├── generated/ # AI-generated scripts +│ ├── check_test_toml_paths.py # Audit script (CI gate) +│ ├── docker_build.sh +│ └── docker_run.sh +├── sloppy.py # Main entry point +├── config.toml # Global configuration +├── manual_slop.toml # Active project config (current) +└── credentials.toml # API keys (gitignored) ``` diff --git a/docs/guide_ai_client.md b/docs/guide_ai_client.md index eb98443d..86d1e386 100644 --- a/docs/guide_ai_client.md +++ b/docs/guide_ai_client.md @@ -6,14 +6,14 @@ ## 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 8 providers split into 3 API shapes: +The 7 providers split into 3 API shapes: - **Native SDK**: Gemini (google-genai), Anthropic (anthropic), Qwen (DashScope) - **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). @@ -21,7 +21,7 @@ The OpenAI-compatible vendors all call the shared helper in `src/openai_compatib ## 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. @@ -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, ...) │ -│ │ -│ 1. _send_lock.acquire() — serialize all calls │ -│ 2. Read _provider / _model │ +│ ai_client.send(md_content, user_message, ...) │ +│ │ +│ 1. _send_lock.acquire() — serialize all calls │ +│ 2. Read _provider / _model │ │ 3. Route to provider-specific _send_() │ -│ 4. Return str response │ +│ 4. Return str response │ └─────────────────┬───────────────────────────────┘ - │ dispatches based on _provider - ▼ - ┌────────┬─────────┬────────┬──────────┐ - ▼ ▼ ▼ ▼ ▼ -_gemini _anthropic _deepseek _minimax _gemini_cli - (subprocess) + │ dispatches based on _provider + ▼ + ┌────────┬─────────┬────────┬──────────┐ + ▼ ▼ ▼ ▼ ▼ +_gemini _anthropic _deepseek _minimax _gemini_cli + (subprocess) ``` --- @@ -94,18 +94,18 @@ _gemini_cli_adapter: Optional[GeminiCliAdapter] = None ```python def send( - md_content: str, - user_message: str, - base_dir: str = ".", - file_items: list[dict] | None = None, - discussion_history: str = "", - stream: bool = False, - pre_tool_callback: Optional[Callable] = None, - qa_callback: Optional[Callable] = None, - enable_tools: bool = True, - stream_callback: Optional[Callable] = None, - patch_callback: Optional[Callable] = None, - rag_engine: Optional[Any] = None, + md_content: str, + user_message: str, + base_dir: str = ".", + file_items: list[dict] | None = None, + discussion_history: str = "", + stream: bool = False, + pre_tool_callback: Optional[Callable] = None, + qa_callback: Optional[Callable] = None, + enable_tools: bool = True, + stream_callback: Optional[Callable] = None, + patch_callback: Optional[Callable] = None, + rag_engine: Optional[Any] = None, ) -> 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 ```python -ai_client.reset_session() # Clears all provider state, history, cache +ai_client.reset_session() # Clears all provider state, history, cache ``` ### Event Hooks @@ -171,10 +171,10 @@ ai_client.events.on("my_event", my_handler) ### Comms Log ```python -ai_client._append_comms(direction, kind, payload) # Add entry -ai_client.get_comms_log() # Read all -ai_client.clear_comms_log() # Clear -ai_client.get_token_stats(md_content) # Estimate token usage +ai_client._append_comms(direction, kind, payload) # Add entry +ai_client.get_comms_log() # Read all +ai_client.clear_comms_log() # Clear +ai_client.get_token_stats(md_content) # Estimate token usage ``` ### Provider Error Taxonomy — Legacy (Pre-Refactor) @@ -187,12 +187,12 @@ ai_client.get_token_stats(md_content) # Estimate token usage ```python class ProviderError(Exception): - kind: str # "quota" | "rate_limit" | "auth" | "balance" | "network" | "unknown" - provider: str - original: Exception + kind: str # "quota" | "rate_limit" | "auth" | "balance" | "network" | "unknown" + provider: str + original: Exception - def ui_message(self) -> str: - """Returns a user-friendly error message.""" + def ui_message(self) -> str: + """Returns a user-friendly error message.""" ``` `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 def _send_(md_content, user_message, ...): - for round in range(MAX_TOOL_ROUNDS + 2): # up to 10 rounds - response = provider_api_call(md_content, user_message, history, tools) - comms_log(direction="IN", kind="response", payload=response) + for round in range(MAX_TOOL_ROUNDS + 2): # up to 10 rounds + response = provider_api_call(md_content, user_message, history, tools) + comms_log(direction="IN", kind="response", payload=response) - if not has_function_calls(response): - return extract_text(response) + if not has_function_calls(response): + return extract_text(response) - for call in response.function_calls: - if pre_tool_callback and pre_tool_callback(...) is rejected: - return rejection_message - tool_result = dispatch(call.name, call.args, base_dir) - append_tool_result_to_history(call, tool_result) + for call in response.function_calls: + if pre_tool_callback and pre_tool_callback(...) is rejected: + return rejection_message + tool_result = dispatch(call.name, call.args, base_dir) + append_tool_result_to_history(call, tool_result) - # Context refresh: re-read all tracked files (mtime check) - _reread_file_items(file_items) + # Context refresh: re-read all tracked files (mtime check) + _reread_file_items(file_items) - # Truncate tool outputs at _history_trunc_limit - truncate_tool_outputs(history) + # Truncate tool outputs at _history_trunc_limit + truncate_tool_outputs(history) - # Cumulative byte check - if cumulative_tool_bytes > 500_000: - inject_warning() + # Cumulative byte check + if cumulative_tool_bytes > 500_000: + inject_warning() - return final_response + return final_response ``` The constants: @@ -273,7 +273,7 @@ The constants: - **History trimming**: similar to Anthropic (drop turn pairs at threshold) - **History repair**: `_repair_minimax_history` -### Gemini CLI +### - **Subprocess adapter**: `GeminiCliAdapter` in `src/gemini_cli_adapter.py` - **Persistent session**: CLI maintains its own session ID @@ -288,9 +288,9 @@ The constants: ```python if total_in > _GEMINI_MAX_INPUT_TOKENS * 0.4: - while len(hist) > 4 and total_in > _GEMINI_MAX_INPUT_TOKENS * 0.3: - hist.pop(0) # Assistant - hist.pop(0) # User + while len(hist) > 4 and total_in > _GEMINI_MAX_INPUT_TOKENS * 0.3: + hist.pop(0) # Assistant + hist.pop(0) # User ``` ### Anthropic (180K limit) @@ -314,8 +314,8 @@ No built-in trimming (relies on the caller to keep history short). ### Gemini Server-Side Cache ```python -_gemini_cache_md_hash: Optional[str] = None # Hash of cached content -_gemini_cache_created_at: Optional[float] = None # Monotonic time +_gemini_cache_md_hash: Optional[str] = None # Hash of cached content +_gemini_cache_created_at: Optional[float] = None # Monotonic time ``` 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 3. If changed: re-read content, store `old_content` for diffing, update `mtime` 4. Changed files are diffed via `_build_file_diff_text`: - - Files ≤ 200 lines: emit full content - - Files > 200 lines with `old_content`: emit `difflib.unified_diff` + - Files ≤ 200 lines: emit full content + - 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}` 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 def run_tier4_analysis(stderr: str) -> str: - """Stateless Tier 4 QA analysis of an error message.""" - # Uses a dedicated system prompt for error triage - # Returns analysis text (root cause, suggested fix) - # Does NOT modify any code — analysis only + """Stateless Tier 4 QA analysis of an error message.""" + # Uses a dedicated system prompt for error triage + # Returns analysis text (root cause, suggested fix) + # Does NOT modify any code — analysis only ``` For Tier 4 patch generation: ```python def run_tier4_patch_generation(error: str, file_context: str) -> str: - """Generate a unified diff patch from an error and file context.""" - # Returns the patch as a string - # The caller (typically the patch modal) presents it for human review + """Generate a unified diff patch from an error and file context.""" + # Returns the patch as a string + # 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 def test_set_provider(): - from src import ai_client - ai_client.set_provider("anthropic", "claude-3-5-sonnet-latest") - assert ai_client.get_provider() == "anthropic" - ai_client.reset_session() # Cleanup + from src import ai_client + ai_client.set_provider("anthropic", "claude-3-5-sonnet-latest") + assert ai_client.get_provider() == "anthropic" + ai_client.reset_session() # Cleanup ``` ### Mocked Tests @@ -428,12 +428,12 @@ def test_set_provider(): from unittest.mock import patch def test_send_routes_to_provider(monkeypatch): - with patch.object(ai_client, "_send_anthropic", return_value="mocked") as m: - ai_client.set_provider("anthropic", "claude-3-5-sonnet-latest") - result = ai_client.send("system", "user") - assert result == "mocked" - m.assert_called_once() - ai_client.reset_session() + with patch.object(ai_client, "_send_anthropic", return_value="mocked") as m: + ai_client.set_provider("anthropic", "claude-3-5-sonnet-latest") + result = ai_client.send("system", "user") + assert result == "mocked" + m.assert_called_once() + ai_client.reset_session() ``` ### Integration (real API) @@ -451,7 +451,7 @@ canonical reference is ### Result-Based Returns All `_send__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 exceptions are caught at the boundary (`src/openai_compatible.py`, `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 - **`ai_client.send(...)`** — the public API. Returns - `Result[str]` (with `errors: list[ErrorInfo]` as a side-channel field). - Accepts 13+ parameters including 8 callbacks. - Internally calls `_send_()` for the active provider (the - vendor functions return `Result[str]` directly). + `Result[str]` (with `errors: list[ErrorInfo]` as a side-channel field). + Accepts 13+ parameters including 8 callbacks. + Internally calls `_send_()` for the active provider (the + vendor functions return `Result[str]` directly). ### Example @@ -482,9 +482,9 @@ from src.result_types import ErrorKind r = ai_client.send("system prompt", "user message") if not r.ok: - for err in r.errors: - log.error(err.ui_message()) - # err.kind is one of ErrorKind.*; err.source is "ai_client." + for err in r.errors: + log.error(err.ui_message()) + # err.kind is one of ErrorKind.*; err.source is "ai_client." # use r.data regardless (it's the zero-initialized "" on failure) print(r.data) ``` @@ -492,10 +492,10 @@ print(r.data) ### Migration Notes for Existing Callers - All production call sites and tests now use `send()`. The - legacy `send()` function was removed in the - `public_api_migration_and_ui_polish_20260615` track. + legacy `send()` function was removed in the + `public_api_migration_and_ui_polish_20260615` track. - Tests that mock `ai_client._send_` should use the - `Result(data=...)` return value pattern. + `Result(data=...)` return value pattern. ### See Also (in-doc) @@ -532,34 +532,34 @@ Added 2026-06-06 by the `qwen_llama_grok_integration_20260606` track. Operates o ```python @dataclass(frozen=True) class NormalizedResponse: - text: str - tool_calls: list[dict[str, Any]] - usage_input_tokens: int - usage_output_tokens: int - usage_cache_read_tokens: int - usage_cache_creation_tokens: int - raw_response: Any + text: str + tool_calls: list[dict[str, Any]] + usage_input_tokens: int + usage_output_tokens: int + usage_cache_read_tokens: int + usage_cache_creation_tokens: int + raw_response: Any @dataclass class OpenAICompatibleRequest: - messages: list[dict[str, Any]] - model: str - temperature: float = 0.0 - top_p: float = 1.0 - max_tokens: int = 8192 - tools: Optional[list[dict[str, Any]]] = None - tool_choice: str = "auto" - stream: bool = False - stream_callback: Optional[Callable[[str], None]] = None + messages: list[dict[str, Any]] + model: str + temperature: float = 0.0 + top_p: float = 1.0 + max_tokens: int = 8192 + tools: Optional[list[dict[str, Any]]] = None + tool_choice: str = "auto" + stream: bool = False + stream_callback: Optional[Callable[[str], None]] = None ``` ### The Function ```python def send_openai_compatible( - client: Any, # openai.OpenAI client with vendor-specific base_url + auth - request: OpenAICompatibleRequest, - *, capabilities: "VendorCapabilities", # from src/ai_client.py #region: Vendor Capabilities + client: Any, # openai.OpenAI client with vendor-specific base_url + auth + request: OpenAICompatibleRequest, + *, capabilities: "VendorCapabilities", # from src/ai_client.py #region: Vendor Capabilities ) -> NormalizedResponse: ``` @@ -577,16 +577,16 @@ The function: ```python # _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, ...): - client = _ensure_grok_client() # openai.OpenAI(api_key=..., base_url="https://api.x.ai/v1") - with _grok_history_lock: - # ... build messages, append user, system + context ... - request = OpenAICompatibleRequest( - messages=messages, model=_model, stream=stream, - stream_callback=stream_callback, - ) - caps = get_capabilities("grok", _model) - response = send_openai_compatible(client, request, capabilities=caps) - # ... append to history, return response.text ... + client = _ensure_grok_client() # openai.OpenAI(api_key=..., base_url="https://api.x.ai/v1") + with _grok_history_lock: + # ... build messages, append user, system + context ... + request = OpenAICompatibleRequest( + messages=messages, model=_model, stream=stream, + stream_callback=stream_callback, +) + caps = get_capabilities("grok", _model) + response = send_openai_compatible(client, request, capabilities=caps) + # ... append to history, return response.text ... ``` ### 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 def run_with_tool_loop( - client: Any, - request: OpenAICompatibleRequest | Callable[[int], OpenAICompatibleRequest], - *, - capabilities: "VendorCapabilities", - pre_tool_callback: Optional[Callable] = None, - qa_callback: Optional[Callable] = None, - stream_callback: Optional[Callable[[str], None]] = None, - patch_callback: Optional[Callable] = None, - base_dir: str, - vendor_name: str, - history_lock: Optional[threading.Lock] = None, - history: Optional[list] = None, - trim_func: Optional[Callable] = None, - send_func: Optional[Callable[[int], "NormalizedResponse"]] = None, - on_pre_dispatch: Optional[Callable] = None, + client: Any, + request: OpenAICompatibleRequest | Callable[[int], OpenAICompatibleRequest], + *, + capabilities: "VendorCapabilities", + pre_tool_callback: Optional[Callable] = None, + qa_callback: Optional[Callable] = None, + stream_callback: Optional[Callable[[str], None]] = None, + patch_callback: Optional[Callable] = None, + base_dir: str, + vendor_name: str, + history_lock: Optional[threading.Lock] = None, + history: Optional[list] = None, + trim_func: Optional[Callable] = None, + send_func: Optional[Callable[[int], "NormalizedResponse"]] = None, + on_pre_dispatch: Optional[Callable] = None, ) -> str: ``` **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). -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): - `_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: ```python 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. @@ -714,11 +714,11 @@ The test in `tests/test_aggregate_caching.py` ensures the first N characters of ```python def test_aggregate_stable_to_volatile_ordering(): - ctrl = mock_app_controller() - turn1 = aggregate.build_initial_context(ctrl, user_message="first") - turn2 = aggregate.build_initial_context(ctrl, user_message="second") - N = aggregate.stable_prefix_length(ctrl) - assert turn1[:N] == turn2[:N], f"Stable prefix mismatch: {turn1[:N]!r} != {turn2[:N]!r}" + ctrl = mock_app_controller() + turn1 = aggregate.build_initial_context(ctrl, user_message="first") + turn2 = aggregate.build_initial_context(ctrl, user_message="second") + N = aggregate.stable_prefix_length(ctrl) + 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. @@ -729,17 +729,17 @@ def test_aggregate_stable_to_volatile_ordering(): ```python def _send_anthropic(messages, *, cache_prefix_chars=None): - if cache_prefix_chars is not None: - content_blocks = cache_prefix_blocks(messages, cache_prefix_chars) - else: - content_blocks = messages + if cache_prefix_chars is not None: + content_blocks = cache_prefix_blocks(messages, cache_prefix_chars) + else: + content_blocks = messages - response = anthropic_client.messages.create( - model=model, - max_tokens=8192, - messages=[{"role": "user", "content": content_blocks}], - ) - return _result_with_usage(response.content, response.usage, messages) + response = anthropic_client.messages.create( + model=model, + max_tokens=8192, + messages=[{"role": "user", "content": content_blocks}], +) + 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). @@ -750,17 +750,17 @@ def _send_anthropic(messages, *, cache_prefix_chars=None): ```python def _send_gemini(messages, *, cache_ttl_seconds=3600): - if cache_ttl_seconds > 0: - cached_content = genai_client.caches.create( - model=model, contents=stable_prefix_messages, ttl=f"{cache_ttl_seconds}s", - ) - response = genai_client.models.generate_content( - model=model, contents=volatile_messages, - config=genai.types.GenerateContentConfig(cached_content=cached_content.name), - ) - else: - response = genai_client.models.generate_content(model=model, contents=messages) - return _result_with_usage(response.text, response.usage_metadata, messages) + if cache_ttl_seconds > 0: + cached_content = genai_client.caches.create( + model=model, contents=stable_prefix_messages, ttl=f"{cache_ttl_seconds}s", +) + response = genai_client.models.generate_content( + model=model, contents=volatile_messages, + config=genai.types.GenerateContentConfig(cached_content=cached_content.name), +) + else: + response = genai_client.models.generate_content(model=model, contents=messages) + return _result_with_usage(response.text, response.usage_metadata, messages) ``` **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 @dataclass class DiscussionCacheState: - discussion_id: str - provider: str - cached_at: datetime - expires_at: Optional[datetime] # None for OpenAI implicit - hit_count: int = 0 - tokens_cached: int = 0 - last_invalidated_at: Optional[datetime] = None - caching_enabled: bool = True + discussion_id: str + provider: str + cached_at: datetime + expires_at: Optional[datetime] # None for OpenAI implicit + hit_count: int = 0 + tokens_cached: int = 0 + last_invalidated_at: Optional[datetime] = None + caching_enabled: bool = True ``` **The Hook API additions:** ``` -GET /api/cache # list all discussion cache states -GET /api/cache/ # get one +GET /api/cache # list all discussion cache states +GET /api/cache/ # get one POST /api/cache//invalidate POST /api/cache//disable POST /api/cache//enable @@ -809,15 +809,15 @@ POST /api/cache//enable ```python def _send_claude_code(message, model, *, allowed_tools=None, max_turns=1): - options = ClaudeAgentOptions( - model=None if not model or model == "default" else model, - max_turns=max_turns, - tools=list(allowed_tools) if allowed_tools else [], - allowed_tools=list(allowed_tools) if allowed_tools else [], - cwd=os.getcwd(), - ) - # ... claude_agent_sdk.query(prompt=message, options=options) - return _result_with_usage(text, usage, message) + options = ClaudeAgentOptions( + model=None if not model or model == "default" else model, + max_turns=max_turns, + tools=list(allowed_tools) if allowed_tools else [], + allowed_tools=list(allowed_tools) if allowed_tools else [], + cwd=os.getcwd(), +) + # ... claude_agent_sdk.query(prompt=message, options=options) + return _result_with_usage(text, usage, message) ``` ### The cross-references diff --git a/docs/guide_architecture.md b/docs/guide_architecture.md index 41b5e6ac..b965e281 100644 --- a/docs/guide_architecture.md +++ b/docs/guide_architecture.md @@ -20,20 +20,20 @@ The codebase is organized into a `src/` layout to separate implementation from c ``` manual_slop/ -├── conductor/ # Conductor tracks, specs, and plans -├── docs/ # Deep-dive architectural documentation -├── logs/ # Session logs, agent traces, and errors -├── scripts/ # Build, migration, and IPC bridge scripts -├── src/ # Core Python implementation -│ ├── ai_client.py # LLM provider abstraction -│ ├── gui_2.py # Main ImGui application -│ ├── mcp_client.py # MCP tool implementation -│ └── ... # Other core modules -├── tests/ # Pytest suite and simulation fixtures -├── simulation/ # Workflow and agent simulation logic -├── sloppy.py # Primary application entry point -├── config.toml # Global application settings -└── manual_slop.toml # Project-specific configuration +├── conductor/ # Conductor tracks, specs, and plans +├── docs/ # Deep-dive architectural documentation +├── logs/ # Session logs, agent traces, and errors +├── scripts/ # Build, migration, and IPC bridge scripts +├── src/ # Core Python implementation +│ ├── ai_client.py # LLM provider abstraction +│ ├── gui_2.py # Main ImGui application +│ ├── mcp_client.py # MCP tool implementation +│ └── ... # Other core modules +├── tests/ # Pytest suite and simulation fixtures +├── simulation/ # Workflow and agent simulation logic +├── sloppy.py # Primary application entry point +├── config.toml # Global application settings +└── manual_slop.toml # Project-specific configuration ``` --- @@ -59,9 +59,9 @@ self._loop_thread.start() # _run_event_loop: def _run_event_loop(self) -> None: - asyncio.set_event_loop(self._loop) - self._loop.create_task(self._process_event_queue()) - self._loop.run_forever() + asyncio.set_event_loop(self._loop) + self._loop.create_task(self._process_event_queue()) + self._loop.run_forever() ``` 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() def get_current_tier() -> Optional[str]: - """Returns the current tier from thread-local storage.""" - return getattr(_local_storage, "current_tier", None) + """Returns the current tier from thread-local storage.""" + return getattr(_local_storage, "current_tier", None) def set_current_tier(tier: Optional[str]) -> None: - """Sets the current tier in thread-local storage.""" - _local_storage.current_tier = tier + """Sets the current tier in thread-local storage.""" + _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. @@ -96,10 +96,10 @@ All cross-thread communication uses one of three patterns: ```python # events.py 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 get(self) -> Tuple[str, Any] + async def put(self, event_name: str, payload: Any = None) -> None + 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. @@ -125,8 +125,8 @@ self._pending_history_adds_lock = threading.Lock() Additional locks: ```python -self._send_thread_lock = threading.Lock() # Guards send_thread creation -self._pending_dialog_lock = threading.Lock() # Guards _pending_dialog + _pending_actions dict +self._send_thread_lock = threading.Lock() # Guards send_thread creation +self._pending_dialog_lock = threading.Lock() # Guards _pending_dialog + _pending_actions dict ``` ### Pattern C: Condition-Variable Dialogs (Bidirectional Blocking) @@ -143,10 +143,10 @@ Three classes in `events.py` (89 lines, no external dependencies beyond `asyncio ```python class EventEmitter: - _listeners: Dict[str, List[Callable]] + _listeners: Dict[str, List[Callable]] - def on(self, event_name: str, callback: Callable) -> None - def emit(self, event_name: str, *args: Any, **kwargs: Any) -> None + def on(self, event_name: str, callback: Callable) -> 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. @@ -159,13 +159,13 @@ Described above in Pattern A. ```python class UserRequestEvent: - prompt: str # User's raw input text - stable_md: str # Generated markdown context (files, screenshots) - file_items: List[Any] # File attachment items for dynamic refresh - disc_text: str # Serialized discussion history - base_dir: str # Working directory for shell commands + prompt: str # User's raw input text + stable_md: str # Generated markdown context (files, screenshots) + file_items: List[Any] # File attachment items for dynamic refresh + disc_text: str # Serialized discussion history + 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`. @@ -180,8 +180,8 @@ The `App.__init__` (lines 152-296) follows this precise order: 1. **Config hydration**: Reads `config.toml` (global) and `.toml` (local). Builds the initial "world view" — tracked files, discussion history, active models. 2. **Thread bootstrapping**: - - Asyncio event loop thread starts (`_loop_thread`). - - `HookServer` starts as a daemon if `test_hooks_enabled` or provider is `gemini_cli`. + - Asyncio event loop thread starts (`_loop_thread`). + - `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. 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 def shutdown(self) -> None: - if self._loop.is_running(): - self._loop.call_soon_threadsafe(self._loop.stop) - if self._loop_thread.is_alive(): - self._loop_thread.join(timeout=2.0) + if self._loop.is_running(): + self._loop.call_soon_threadsafe(self._loop.stop) + if self._loop_thread.is_alive(): + self._loop_thread.join(timeout=2.0) ``` --- @@ -212,25 +212,25 @@ def shutdown(self) -> None: ### Request Flow ``` -GUI Thread Asyncio Thread GUI Thread (next frame) -────────── ────────────── ────────────────────── +GUI Thread Asyncio Thread GUI Thread (next frame) +────────── ────────────── ────────────────────── 1. User clicks "Gen + Send" 2. _handle_generate_send(): - - Compiles md context - - Creates UserRequestEvent - - Enqueues via - run_coroutine_threadsafe ──> 3. _process_event_queue(): - awaits event_queue.get() - routes "user_request" to - _handle_request_event() - 4. Configures ai_client - 5. ai_client.send() BLOCKS - (seconds to minutes) - 6. On completion, enqueues - "response" event back ──> 7. _process_pending_gui_tasks(): - Drains task list under lock - Sets ai_response text - Triggers terminal blink + - Compiles md context + - Creates UserRequestEvent + - Enqueues via + run_coroutine_threadsafe ──> 3. _process_event_queue(): + awaits event_queue.get() + routes "user_request" to + _handle_request_event() + 4. Configures ai_client + 5. ai_client.send() BLOCKS + (seconds to minutes) + 6. On completion, enqueues + "response" event back ──> 7. _process_pending_gui_tasks(): + Drains task list under lock + Sets ai_response text + Triggers terminal blink ``` ### 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 def _process_pending_gui_tasks(self) -> None: - if not self._pending_gui_tasks: - return - with self._pending_gui_tasks_lock: - tasks = self._pending_gui_tasks[:] # Snapshot - self._pending_gui_tasks.clear() # Release lock fast - for task in tasks: - # Process each task outside the lock + if not self._pending_gui_tasks: + return + with self._pending_gui_tasks_lock: + tasks = self._pending_gui_tasks[:] # Snapshot + self._pending_gui_tasks.clear() # Release lock fast + for task in tasks: + # 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. @@ -294,43 +294,43 @@ The "Execution Clutch" ensures every destructive AI action passes through an aud ```python class ConfirmDialog: - _uid: str # uuid4 identifier - _script: str # The PowerShell script text (editable) - _base_dir: str # Working directory - _condition: threading.Condition # Blocking primitive - _done: bool # Signal flag - _approved: bool # User's decision + _uid: str # uuid4 identifier + _script: str # The PowerShell script text (editable) + _base_dir: str # Working directory + _condition: threading.Condition # Blocking primitive + _done: bool # Signal flag + _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: ```python class MMAApprovalDialog: - _ticket_id: str - _payload: str # The step payload (editable) - _condition: threading.Condition - _done: bool - _approved: bool + _ticket_id: str + _payload: str # The step payload (editable) + _condition: threading.Condition + _done: 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: ```python class MMASpawnApprovalDialog: - _ticket_id: str - _role: str # tier3-worker, tier4-qa, etc. - _prompt: str # Spawn prompt (editable) - _context_md: str # Context document (editable) - _condition: threading.Condition - _done: bool - _approved: bool - _abort: bool # Can abort entire track + _ticket_id: str + _role: str # tier3-worker, tier4-qa, etc. + _prompt: str # Spawn prompt (editable) + _context_md: str # Context document (editable) + _condition: threading.Condition + _done: bool + _approved: bool + _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 @@ -338,27 +338,27 @@ class MMASpawnApprovalDialog: Using `ConfirmDialog` as exemplar: ``` - ASYNCIO THREAD (ai_client tool callback) GUI MAIN THREAD - ───────────────────────────────────────── ─────────────── - 1. ai_client calls _confirm_and_run(script) - 2. Creates ConfirmDialog(script, base_dir) - 3. Stores dialog: - - Headless: _pending_actions[uid] = dialog - - GUI mode: _pending_dialog = dialog - 4. If test_hooks_enabled: - pushes to _api_event_queue - 5. dialog.wait() BLOCKS on _condition - 6. Next frame: ImGui renders - _pending_dialog in modal - 7. User clicks Approve/Reject - 8. _handle_approve_script(): - with dialog._condition: - dialog._approved = True - dialog._done = True - dialog._condition.notify_all() - 9. wait() returns (True, potentially_edited_script) - 10. Executes shell_runner.run_powershell() - 11. Returns output to ai_client + ASYNCIO THREAD (ai_client tool callback) GUI MAIN THREAD + ───────────────────────────────────────── ─────────────── + 1. ai_client calls _confirm_and_run(script) + 2. Creates ConfirmDialog(script, base_dir) + 3. Stores dialog: + - Headless: _pending_actions[uid] = dialog + - GUI mode: _pending_dialog = dialog + 4. If test_hooks_enabled: + pushes to _api_event_queue + 5. dialog.wait() BLOCKS on _condition + 6. Next frame: ImGui renders + _pending_dialog in modal + 7. User clicks Approve/Reject + 8. _handle_approve_script(): + with dialog._condition: + dialog._approved = True + dialog._done = True + dialog._condition.notify_all() + 9. wait() returns (True, potentially_edited_script) + 10. Executes shell_runner.run_powershell() + 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. @@ -373,14 +373,14 @@ The `_condition.wait(timeout=0.1)` uses a 100ms polling interval inside a loop ```python def resolve_pending_action(self, action_id: str, approved: bool) -> bool: - with self._pending_dialog_lock: - if action_id in self._pending_actions: - dialog = self._pending_actions[action_id] - with dialog._condition: - dialog._approved = approved - dialog._done = True - dialog._condition.notify_all() - return True + with self._pending_dialog_lock: + if action_id in self._pending_actions: + dialog = self._pending_actions[action_id] + with dialog._condition: + dialog._approved = approved + dialog._done = True + dialog._condition.notify_all() + return True ``` **MMA approval path**: @@ -395,14 +395,14 @@ def resolve_pending_action(self, action_id: str, approved: bool) -> bool: ### Module-Level State ```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" _temperature: float = 0.0 _top_p: float = 1.0 _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: @@ -410,16 +410,16 @@ Per-provider client objects: ```python # Gemini (SDK-managed stateful chat) _gemini_client: genai.Client | None -_gemini_chat: Any # Holds history internally -_gemini_cache: Any # Server-side CachedContent -_gemini_cache_md_hash: str | None # Hash for cache invalidation +_gemini_chat: Any # Holds history internally +_gemini_cache: Any # Server-side CachedContent +_gemini_cache_md_hash: str | None # Hash for cache invalidation _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_CACHE_TTL: int = 3600 # 1-hour; rebuilt at 90% (3240s) +_gemini_cached_file_paths: list[str] # File paths included in the active cache +_GEMINI_CACHE_TTL: int = 3600 # 1-hour; rebuilt at 90% (3240s) # Anthropic (client-managed history) _anthropic_client: anthropic.Anthropic | None -_anthropic_history: list[dict] # Mutable [{role, content}, ...] +_anthropic_history: list[dict] # Mutable [{role, content}, ...] _anthropic_history_lock: threading.Lock # DeepSeek (raw HTTP, client-managed history) @@ -439,27 +439,27 @@ _gemini_cli_adapter: GeminiCliAdapter | None Safety limits: ```python -MAX_TOOL_ROUNDS: int = 10 # Max tool-call loop iterations per send() -_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_MAX_PROMPT_TOKENS: int = 180_000 # 200k limit minus headroom -_GEMINI_MAX_INPUT_TOKENS: int = 900_000 # 1M window minus headroom +MAX_TOOL_ROUNDS: int = 10 # Max tool-call loop iterations per send() +_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_MAX_PROMPT_TOKENS: int = 180_000 # 200k limit minus headroom +_GEMINI_MAX_INPUT_TOKENS: int = 900_000 # 1M window minus headroom ``` ### The `send()` Dispatcher ```python def send(md_content, user_message, base_dir=".", file_items=None, - discussion_history="", stream=False, - pre_tool_callback=None, qa_callback=None, - enable_tools=True, stream_callback=None, patch_callback=None, - rag_engine=None) -> str: - with _send_lock: - if _provider == "gemini": return _send_gemini(...) - elif _provider == "gemini_cli": return _send_gemini_cli(...) - elif _provider == "anthropic": return _send_anthropic(...) - elif _provider == "deepseek": return _send_deepseek(..., stream=stream) - elif _provider == "minimax": return _send_minimax(..., stream=stream) + discussion_history="", stream=False, + pre_tool_callback=None, qa_callback=None, + enable_tools=True, stream_callback=None, patch_callback=None, + rag_engine=None) -> str: + with _send_lock: + if _provider == "gemini": return _send_gemini(...) + elif _provider == "gemini_cli": return _send_gemini_cli(...) + elif _provider == "anthropic": return _send_anthropic(...) + elif _provider == "deepseek": return _send_deepseek(..., 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`. @@ -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. 4. If no function calls or max rounds exceeded: **break**. 5. For each function call: - - If `pre_tool_callback` rejects: return rejection text. - - Dispatch to `mcp_client.dispatch()` or `shell_runner.run_powershell()`. - - After the **last** call of this round: run `_reread_file_items()` for context refresh. - - Truncate tool output at `_history_trunc_limit` chars. - - Accumulate `_cumulative_tool_bytes`. + - If `pre_tool_callback` rejects: return rejection text. + - Dispatch to `mcp_client.dispatch()` or `shell_runner.run_powershell()`. + - After the **last** call of this round: run `_reread_file_items()` for context refresh. + - Truncate tool output at `_history_trunc_limit` chars. + - Accumulate `_cumulative_tool_bytes`. 6. If cumulative bytes > 500KB: inject warning. 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. 3. If changed: re-read content, store `old_content` for diffing, update `mtime`. 4. Changed files are diffed via `_build_file_diff_text`: - - Files <= 200 lines: emit full content. - - Files > 200 lines with `old_content`: emit `difflib.unified_diff`. + - Files <= 200 lines: emit full content. + - 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}`. 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 async def _execute_tool_calls_concurrently( - calls: list[Any], - base_dir: str, - pre_tool_callback: ..., - qa_callback: ..., - r_idx: int, - provider: str, - patch_callback: ... = None, -) -> list[tuple[str, str, str, str]]: # (tool_name, call_id, output, original_name) - ... + calls: list[Any], + base_dir: str, + pre_tool_callback: ..., + qa_callback: ..., + r_idx: int, + provider: str, + patch_callback: ... = None, +) -> list[tuple[str, str, str, str]]: # (tool_name, call_id, output, original_name) + ... ``` ### Per-Call Worker ```python async def _execute_single_tool_call_async( - name: str, args: dict, call_id: str, base_dir: str, - pre_tool_callback, qa_callback, r_idx: int, - tier: str | None = None, - patch_callback = None, + name: str, args: dict, call_id: str, base_dir: str, + pre_tool_callback, qa_callback, r_idx: int, + tier: str | None = None, + patch_callback = None, ) -> 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. @@ -587,11 +587,11 @@ If any individual call raises, `asyncio.gather` with `return_exceptions=True` co ```python def send(md_content, user_message, base_dir=".", file_items=None, ..., - rag_engine: Optional[Any] = None) -> str: - if rag_engine is not None: - retrieved = rag_engine.query(user_message, top_k=5) - md_content = _inject_rag_context(md_content, retrieved) - ... + rag_engine: Optional[Any] = None) -> str: + if rag_engine is not None: + retrieved = rag_engine.query(user_message, top_k=5) + 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). @@ -612,7 +612,7 @@ When a Tier 3 worker's test run fails, the engine can request a Tier 4 patch ins ```python def run_tier4_patch_generation(error: str, file_context: str) -> str: - ... + ... ``` ### Flow @@ -637,7 +637,7 @@ Long discussions accumulate tool outputs and intermediate reasoning that bloat t ```python def run_discussion_compression(discussion_text: str) -> str: - ... + ... ``` ### Flow @@ -649,7 +649,7 @@ def run_discussion_compression(discussion_text: str) -> str: ### 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 def run_subagent_summarization(file_path: str, content: str, is_code: bool, outline: str) -> str: - ... + ... ``` ### When Invoked @@ -688,17 +688,17 @@ Every API interaction is logged to a module-level list with real-time GUI push: ```python def _append_comms(direction: str, kind: str, payload: dict[str, Any]) -> None: - entry = { - "ts": datetime.now().strftime("%H:%M:%S"), - "direction": direction, # "OUT" (to API) or "IN" (from API) - "kind": kind, # "request" | "response" | "tool_call" | "tool_result" - "provider": _provider, - "model": _model, - "payload": payload, - } - _comms_log.append(entry) - if comms_log_callback: - comms_log_callback(entry) # Real-time push to GUI + entry = { + "ts": datetime.now().strftime("%H:%M:%S"), + "direction": direction, # "OUT" (to API) or "IN" (from API) + "kind": kind, # "request" | "response" | "tool_call" | "tool_result" + "provider": _provider, + "model": _model, + "payload": payload, + } + _comms_log.append(entry) + if comms_log_callback: + 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] - -> "running powershell..." -> "powershell done, awaiting AI..." - -> "fetching url..." | "searching web..." - -> "done" | "error" - -> "idle" (on reset) + -> "running powershell..." -> "powershell done, awaiting AI..." + -> "fetching url..." | "searching web..." + -> "done" | "error" + -> "idle" (on reset) ``` ### HITL Dialog State (Binary per type) @@ -748,32 +748,32 @@ Every interaction is designed to be auditable: ```python # Comms log entry (JSON-L) { - "ts": "14:32:05", - "direction": "OUT", - "kind": "tool_call", - "provider": "gemini", - "model": "gemini-2.5-flash-lite", - "payload": { - "name": "run_powershell", - "id": "call_abc123", - "script": "Get-ChildItem" - }, - "source_tier": "Tier 3", - "local_ts": 1709875925.123 + "ts": "14:32:05", + "direction": "OUT", + "kind": "tool_call", + "provider": "gemini", + "model": "gemini-2.5-flash-lite", + "payload": { + "name": "run_powershell", + "id": "call_abc123", + "script": "Get-ChildItem" + }, + "source_tier": "Tier 3", + "local_ts": 1709875925.123 } # Performance metrics (via get_metrics()) { - "fps": 60.0, - "fps_avg": 58.5, - "last_frame_time_ms": 16.67, - "frame_time_ms_avg": 17.1, - "cpu_percent": 12.5, - "cpu_percent_avg": 15.2, - "input_lag_ms": 2.3, - "input_lag_ms_avg": 3.1, - "time_render_mma_dashboard_ms": 5.2, - "time_render_mma_dashboard_ms_avg": 4.8 + "fps": 60.0, + "fps_avg": 58.5, + "last_frame_time_ms": 16.67, + "frame_time_ms_avg": 17.1, + "cpu_percent": 12.5, + "cpu_percent_avg": 15.2, + "input_lag_ms": 2.3, + "input_lag_ms_avg": 3.1, + "time_render_mma_dashboard_ms": 5.2, + "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 class WorkerPool: - def __init__(self, max_workers: int = 4): - self.max_workers = max_workers - self._active: dict[str, threading.Thread] = {} - self._lock = threading.Lock() - self._semaphore = threading.Semaphore(max_workers) + def __init__(self, max_workers: int = 4): + self.max_workers = max_workers + self._active: dict[str, threading.Thread] = {} + self._lock = threading.Lock() + self._semaphore = threading.Semaphore(max_workers) - def spawn(self, ticket_id: str, target: Callable, args: tuple) -> Optional[threading.Thread]: - with self._lock: - if len(self._active) >= self.max_workers: - return None - - def wrapper(*a, **kw): - try: - with self._semaphore: - target(*a, **kw) - finally: - with self._lock: - self._active.pop(ticket_id, None) - - t = threading.Thread(target=wrapper, args=args, daemon=True) - with self._lock: - self._active[ticket_id] = t - t.start() - return t + def spawn(self, ticket_id: str, target: Callable, args: tuple) -> Optional[threading.Thread]: + with self._lock: + if len(self._active) >= self.max_workers: + return None + + def wrapper(*a, **kw): + try: + with self._semaphore: + target(*a, **kw) + finally: + with self._lock: + self._active.pop(ticket_id, None) + + t = threading.Thread(target=wrapper, args=args, daemon=True) + with self._lock: + self._active[ticket_id] = t + t.start() + return t ``` **Key behaviors**: @@ -825,22 +825,22 @@ The `ConductorEngine` orchestrates ticket execution within a track: ```python class ConductorEngine: - def __init__(self, track: Track, event_queue: Optional[SyncEventQueue] = None, - auto_queue: bool = False) -> None: - self.track = track - self.event_queue = event_queue - self.dag = TrackDAG(self.track.tickets) - self.engine = ExecutionEngine(self.dag, auto_queue=auto_queue) - self.pool = WorkerPool(max_workers=4) - self._abort_events: dict[str, threading.Event] = {} - self._pause_event = threading.Event() - self._tier_usage_lock = threading.Lock() - self.tier_usage = { - "Tier 1": {"input": 0, "output": 0, "model": "gemini-3.1-pro-preview"}, - "Tier 2": {"input": 0, "output": 0, "model": "gemini-3-flash-preview"}, - "Tier 3": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite"}, - "Tier 4": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite"}, - } + def __init__(self, track: Track, event_queue: Optional[SyncEventQueue] = None, + auto_queue: bool = False) -> None: + self.track = track + self.event_queue = event_queue + self.dag = TrackDAG(self.track.tickets) + self.engine = ExecutionEngine(self.dag, auto_queue=auto_queue) + self.pool = WorkerPool(max_workers=4) + self._abort_events: dict[str, threading.Event] = {} + self._pause_event = threading.Event() + self._tier_usage_lock = threading.Lock() + self.tier_usage = { + "Tier 1": {"input": 0, "output": 0, "model": "gemini-3.1-pro-preview"}, + "Tier 2": {"input": 0, "output": 0, "model": "gemini-3-flash-preview"}, + "Tier 3": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite"}, + "Tier 4": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite"}, + } ``` **Main execution loop** (`run` method): @@ -864,17 +864,17 @@ self._abort_events[ticket.id] = threading.Event() # Worker checks abort at three points: # 1. Before major work if abort_event.is_set(): - ticket.status = "killed" - return "ABORTED" + ticket.status = "killed" + return "ABORTED" # 2. Before tool execution (in clutch_callback) if abort_event.is_set(): - return False # Reject tool + return False # Reject tool # 3. After blocking send() returns if abort_event.is_set(): - ticket.status = "killed" - return "ABORTED" + ticket.status = "killed" + return "ABORTED" ``` --- @@ -907,21 +907,21 @@ The `ProviderError` class provides structured error classification: ```python class ProviderError(Exception): - def __init__(self, kind: str, provider: str, original: Exception): - self.kind = kind # "quota" | "rate_limit" | "auth" | "balance" | "network" | "unknown" - self.provider = provider - self.original = original - - def ui_message(self) -> str: - labels = { - "quota": "QUOTA EXHAUSTED", - "rate_limit": "RATE LIMITED", - "auth": "AUTH / API KEY ERROR", - "balance": "BALANCE / BILLING ERROR", - "network": "NETWORK / CONNECTION ERROR", - "unknown": "API ERROR", - } - return f"[{self.provider.upper()} {labels.get(self.kind, 'API ERROR')}]\n\n{self.original}" + def __init__(self, kind: str, provider: str, original: Exception): + self.kind = kind # "quota" | "rate_limit" | "auth" | "balance" | "network" | "unknown" + self.provider = provider + self.original = original + + def ui_message(self) -> str: + labels = { + "quota": "QUOTA EXHAUSTED", + "rate_limit": "RATE LIMITED", + "auth": "AUTH / API KEY ERROR", + "balance": "BALANCE / BILLING ERROR", + "network": "NETWORK / CONNECTION ERROR", + "unknown": "API ERROR", + } + return f"[{self.provider.upper()} {labels.get(self.kind, 'API ERROR')}]\n\n{self.original}" ``` ### Error Recovery Patterns @@ -944,29 +944,29 @@ class ProviderError(Exception): **Gemini (40% threshold)**: ```python if total_in > _GEMINI_MAX_INPUT_TOKENS * 0.4: - while len(hist) > 4 and total_in > _GEMINI_MAX_INPUT_TOKENS * 0.3: - # Drop oldest message pairs - hist.pop(0) # Assistant - hist.pop(0) # User + while len(hist) > 4 and total_in > _GEMINI_MAX_INPUT_TOKENS * 0.3: + # Drop oldest message pairs + hist.pop(0) # Assistant + hist.pop(0) # User ``` **Anthropic (180K limit)**: ```python def _trim_anthropic_history(system_blocks, history): - est = _estimate_prompt_tokens(system_blocks, history) - while len(history) > 3 and est > _ANTHROPIC_MAX_PROMPT_TOKENS: - # Drop turn pairs, preserving tool_result chains - ... + est = _estimate_prompt_tokens(system_blocks, history) + while len(history) > 3 and est > _ANTHROPIC_MAX_PROMPT_TOKENS: + # Drop turn pairs, preserving tool_result chains + ... ``` ### Tool Output Budget ```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: - # Inject warning, force final answer - parts.append("SYSTEM WARNING: Cumulative tool output exceeded 500KB budget.") + # Inject warning, force final answer + parts.append("SYSTEM WARNING: Cumulative tool output exceeded 500KB budget.") ``` ### 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]] = {} def get_cached_tree(self, path: Optional[str], code: str) -> tree_sitter.Tree: - mtime = p.stat().st_mtime if p.exists() else 0.0 - if path in _ast_cache: - cached_mtime, tree = _ast_cache[path] - if cached_mtime == mtime: - return tree - # Parse and cache with simple LRU (max 10 entries) - if len(_ast_cache) >= 10: - del _ast_cache[next(iter(_ast_cache))] - tree = self.parse(code) - _ast_cache[path] = (mtime, tree) - return tree + mtime = p.stat().st_mtime if p.exists() else 0.0 + if path in _ast_cache: + cached_mtime, tree = _ast_cache[path] + if cached_mtime == mtime: + return tree + # Parse and cache with simple LRU (max 10 entries) + if len(_ast_cache) >= 10: + del _ast_cache[next(iter(_ast_cache))] + tree = self.parse(code) + _ast_cache[path] = (mtime, tree) + return tree ``` --- diff --git a/docs/guide_context_aggregation.md b/docs/guide_context_aggregation.md index ef6c321a..a98c9635 100644 --- a/docs/guide_context_aggregation.md +++ b/docs/guide_context_aggregation.md @@ -24,15 +24,15 @@ This is one of the most-touched modules in the project. After the nagent_review, ``` aggregate.run(config, aggregation_strategy) - ├─ find_next_increment(output_dir, namespace) # next file number for output - ├─ build_file_items(base_dir, files) # read + view-mode transform - ├─ build_markdown_from_items(file_items, ...) # compose sections - │ ├─ ## Files (or Files (Summary) or Files (Tier 3 - Focused)) - │ │ └─ _build_files_section_from_items OR summarize.build_summary_markdown - │ ├─ ## Screenshots (if any) - │ ├─ ## Beads Mode: Progress Track (if execution_mode == "beads") - │ └─ ## Discussion History (if any) - └─ output_file.write_text(markdown) + ├─ find_next_increment(output_dir, namespace) # next file number for output + ├─ build_file_items(base_dir, files) # read + view-mode transform + ├─ build_markdown_from_items(file_items, ...) # compose sections + │ ├─ ## Files (or Files (Summary) or Files (Tier 3 - Focused)) + │ │ └─ _build_files_section_from_items OR summarize.build_summary_markdown + │ ├─ ## Screenshots (if any) + │ ├─ ## Beads Mode: Progress Track (if execution_mode == "beads") + │ └─ ## Discussion History (if any) + └─ 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. @@ -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: ```python -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)) +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)) else: # auto - 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)) + 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)) ``` 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 @dataclass class FileItem: - path: str # the artifact identity (path-keyed, no inode) - 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 - view_mode: str = 'full' # one of: full, summary, skeleton, outline, masked, custom, none - selected: bool = False # for batch operations (the Context Panel multi-select) - ast_signatures: bool = False # include only signatures (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) - custom_slices: list[dict] # Fuzzy Anchor slices: {start_line, end_line, tag, comment, ...} - injected_at: Optional[float] # timestamp of last injection + path: str # the artifact identity (path-keyed, no inode) + 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 + view_mode: str = 'full' # one of: full, summary, skeleton, outline, masked, custom, none + selected: bool = False # for batch operations (the Context Panel multi-select) + ast_signatures: bool = False # include only signatures (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) + custom_slices: list[dict] # Fuzzy Anchor slices: {start_line, end_line, tag, comment, ...} + 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. @@ -114,13 +114,13 @@ A `custom_slices` entry is `{start_line, end_line, tag, comment, ...}` (plus Fuz ```python { - "start_line": int, # 1-based original line - "end_line": int, # 1-based original line (inclusive) - "tag": str|None, # human label, defaults to None - "comment": str|None, # human comment, defaults to None - "content_hash": str, # SHA-256 of the slice content (for Fuzzy Anchor stability) - "anchor_lines": [str, ...],# surrounding context for re-resolution - # plus the original positioning metadata + "start_line": int, # 1-based original line + "end_line": int, # 1-based original line (inclusive) + "tag": str|None, # human label, defaults to None + "comment": str|None, # human comment, defaults to None + "content_hash": str, # SHA-256 of the slice content (for Fuzzy Anchor stability) + "anchor_lines": [str, ...],# surrounding context for re-resolution + # plus the original positioning metadata } ``` @@ -144,10 +144,10 @@ Multiple slices in a file are joined with `\n\n`. ```python @dataclass class ContextPreset: - name: str # the preset name (used as TOML key) - files: list[ContextFileEntry] = field(default_factory=list) - screenshots: list[str] = field(default_factory=list) - description: str = "" + name: str # the preset name (used as TOML key) + files: list[ContextFileEntry] = field(default_factory=list) + screenshots: list[str] = field(default_factory=list) + 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. @@ -170,16 +170,16 @@ class ContextPreset: ```python def build_discussion_section(history: list[Any]) -> str: - sections = [] - for i, entry in enumerate(history, start=1): - if isinstance(entry, dict): - role = entry.get("role", "Unknown") - content = entry.get("content", "").strip() - text = f"{role}: {content}" - else: - text = str(entry).strip() - sections.append(f"### Discussion Excerpt {i}\n\n{text}") - return "\n\n---\n\n".join(sections) + sections = [] + for i, entry in enumerate(history, start=1): + if isinstance(entry, dict): + role = entry.get("role", "Unknown") + content = entry.get("content", "").strip() + text = f"{role}: {content}" + else: + text = str(entry).strip() + sections.append(f"### Discussion Excerpt {i}\n\n{text}") + 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. @@ -231,7 +231,7 @@ For Tier 3, `force_full` is treated as a *focus flag*: ```python 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. @@ -244,8 +244,8 @@ So a `force_full=True` file in a Tier 3 worker context is treated as a focus fil ```python for item in file_items: - if not item.get("auto_aggregate", True): continue - # ... build section + if not item.get("auto_aggregate", True): continue + # ... 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. @@ -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`) - **ContextPreset schema:** `src/context_presets.py:ContextPreset` (moved out of `src/models.py`) - **ContextPresetManager:** `src/context_presets.py` (30 lines) -- **AI client consumption:** `src/ai_client.py:_send_` × 8 (gemini, anthropic, gemini_cli, deepseek, minimax, qwen, grok, llama), see `guide_ai_client.md` +- **AI client consumption:** `src/ai_client.py:_send_` × 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` - **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"` diff --git a/docs/guide_models.md b/docs/guide_models.md index f5c3f8dc..3d0ae7ef 100644 --- a/docs/guide_models.md +++ b/docs/guide_models.md @@ -19,12 +19,12 @@ The dataclass definitions, `DEFAULT_TOOL_CATEGORIES`, the `__getattr__` shim, an ```python from src.mma import TrackMetadata -Metadata = TrackMetadata # legacy class name re-export +Metadata = TrackMetadata # legacy class name re-export def __getattr__(name: str) -> Any: if name == "PROVIDERS": - from src import ai_client - return ai_client.PROVIDERS + from src import ai_client + return ai_client.PROVIDERS 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 | |---|---|---| -| `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 | | 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 | @@ -132,4 +132,4 @@ All v2 fields default to `False`. The dataclass is `frozen=True`; per-vendor ent - **`src/type_aliases.py`** — The typed boundary + per-aggregate dataclasses - **`src/mcp_tool_specs.py`** — The typed `ToolSpec` registry (45 tools) - **`src/result_types.py`** — `Result[T]`, `ErrorInfo`, `ErrorKind` for data-oriented error handling -- **[conductor/tracks/nagent_review_20260608/report.md §6](../conductor/tracks/nagent_review_20260608/report.md)** — Deep-dive on the `FileItem` schema as Manual Slop's strongest curation dimension \ No newline at end of file +- **[conductor/tracks/nagent_review_20260608/report.md §6](../conductor/tracks/nagent_review_20260608/report.md)** — Deep-dive on the `FileItem` schema as Manual Slop's strongest curation dimension diff --git a/docs/guide_simulations.md b/docs/guide_simulations.md index 023dd21c..37cd38ab 100644 --- a/docs/guide_simulations.md +++ b/docs/guide_simulations.md @@ -29,13 +29,13 @@ Defined in `tests/conftest.py`, this session-scoped fixture manages the lifecycl ```python @pytest.fixture(scope="session") def live_gui(request) -> Generator["_LiveGuiHandle", None, None]: - process = subprocess.Popen( - ["uv", "run", "python", "-u", gui_script, "--enable-test-hooks"], - stdout=log_file, stderr=log_file, text=True, - creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if os.name == 'nt' else 0 - ) - # ... (readiness polling + xdist coordination) ... - yield _LiveGuiHandle(process, gui_script, workspace=temp_workspace) + process = subprocess.Popen( + ["uv", "run", "python", "-u", gui_script, "--enable-test-hooks"], + stdout=log_file, stderr=log_file, text=True, + creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if os.name == 'nt' else 0 +) + # ... (readiness polling + xdist coordination) ... + yield _LiveGuiHandle(process, gui_script, workspace=temp_workspace) ``` - **`-u` flag**: Disables output buffering for real-time log capture. @@ -45,13 +45,13 @@ def live_gui(request) -> Generator["_LiveGuiHandle", None, None]: **Readiness polling:** ```python -max_retries = 15 # seconds +max_retries = 15 # seconds while time.time() - start_time < max_retries: - response = requests.get("http://127.0.0.1:8999/status", timeout=0.5) - if response.status_code == 200: - ready = True; break - if process.poll() is not None: break # Process died early - time.sleep(0.5) + response = requests.get("http://127.0.0.1:8999/status", timeout=0.5) + if response.status_code == 200: + ready = True; break + if process.poll() is not None: break # Process died early + 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. @@ -62,11 +62,11 @@ Polls `GET /status` every 500ms for up to 15 seconds. Checks `process.poll()` ea ```python finally: - client = ApiHookClient() - client.reset_session() # Clean GUI state before killing - time.sleep(0.5) - kill_process_tree(process.pid) - log_file.close() + client = ApiHookClient() + client.reset_session() # Clean GUI state before killing + time.sleep(0.5) + kill_process_tree(process.pid) + log_file.close() ``` 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 @pytest.fixture(autouse=True) def reset_ai_client() -> Generator[None, None, None]: - ai_client.reset_session() - ai_client.set_provider("gemini", "gemini-2.5-flash-lite") - yield + ai_client.reset_session() + ai_client.set_provider("gemini", "gemini-2.5-flash-lite") + yield ``` 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 @pytest.fixture(autouse=True) def isolate_workspace(tmp_path_factory, monkeypatch) -> Generator[None, None, None]: - # Redirects the path resolution layer to a temp directory - # Prevents tests from writing to the user's actual project - ... + # Redirects the path resolution layer to a temp directory + # 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. @@ -117,8 +117,8 @@ This is the primary mechanism for satisfying the **Artifact Isolation** rule in ```python @pytest.fixture(autouse=True) 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. @@ -147,11 +147,11 @@ Structured diagnostic logging for test telemetry: ```python class VerificationLogger: - 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')}") + 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')}") - def log_state(self, field: str, before: Any, after: Any, delta: Any = None) - def finalize(self, description: str, status: str, result_msg: str) + def log_state(self, field: str, before: Any, after: Any, delta: Any = None) + def finalize(self, description: str, status: str, result_msg: str) ``` Output format: fixed-width column table (`Field | Before | After | Delta`) written to `logs/test//.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 for _ in range(60): - status = client.get_mma_status() - 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_tool_approval'): client.click('btn_approve_tool') - if status.get('proposed_tracks') and len(status['proposed_tracks']) > 0: break - time.sleep(1) + status = client.get_mma_status() + 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_tool_approval'): client.click('btn_approve_tool') + if status.get('proposed_tracks') and len(status['proposed_tracks']) > 0: break + time.sleep(1) ``` 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 streams = status.get('mma_streams', {}) 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] - if "SUCCESS: Mock Tier 3 worker" in streams[tier3_key]: - streams_found = True + tier3_key = [k for k in streams.keys() if "Tier 3" in k][0] + if "SUCCESS: Mock Tier 3 worker" in streams[tier3_key]: + streams_found = True ``` 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:** ```python -prompt = sys.stdin.read() # Primary: prompt via stdin -sys.argv # Secondary: management command detection -os.environ.get('GEMINI_CLI_HOOK_CONTEXT') # Tertiary: environment variable +prompt = sys.stdin.read() # Primary: prompt via stdin +sys.argv # Secondary: management command detection +os.environ.get('GEMINI_CLI_HOOK_CONTEXT') # Tertiary: environment variable ``` **Management command bypass:** ```python 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: @@ -390,22 +390,22 @@ The headless service uses the **Remote Confirmation Protocol** for HITL: when an ```python class ASTParser: - def __init__(self, language: str = "python"): - self.language = tree_sitter.Language(tree_sitter_python.language()) - self.parser = tree_sitter.Parser(self.language) + def __init__(self, language: str = "python"): + self.language = tree_sitter.Language(tree_sitter_python.language()) + self.parser = tree_sitter.Parser(self.language) - def parse(self, code: str) -> tree_sitter.Tree - def get_skeleton(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 parse(self, code: str) -> tree_sitter.Tree + def get_skeleton(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 ``` **`get_skeleton` algorithm:** 1. Parse code to tree-sitter AST. 2. Walk all `function_definition` nodes. 3. For each body (`block` node): - - If first non-comment child is a docstring: preserve docstring, replace rest with `...`. - - Otherwise: replace entire body with `...`. + - If first non-comment child is a docstring: preserve docstring, replace rest with `...`. + - Otherwise: replace entire body with `...`. 4. Apply edits in reverse byte order (maintains valid offsets). **`get_curated_view` algorithm:** @@ -428,10 +428,10 @@ Token-efficient structural descriptions without AI calls: ```python _SUMMARISERS: dict[str, Callable] = { - ".py": _summarise_python, # imports, classes, methods, functions, constants - ".toml": _summarise_toml, # table keys + array lengths - ".md": _summarise_markdown, # h1-h3 headings - ".ini": _summarise_generic, # line count + preview + ".py": _summarise_python, # imports, classes, methods, functions, constants + ".toml": _summarise_toml, # table keys + array lengths + ".md": _summarise_markdown, # h1-h3 headings + ".ini": _summarise_generic, # line count + preview } ``` @@ -455,8 +455,8 @@ functions: summarise_file, build_summary_markdown ```python class CodeOutliner: - def __init__(self) -> None: ... - def outline(self, code: str) -> str: ... + def __init__(self) -> None: ... + def outline(self, code: str) -> str: ... def get_outline(path: Path, code: str) -> str: ... ``` diff --git a/docs/guide_tools.md b/docs/guide_tools.md index c531484a..687de404 100644 --- a/docs/guide_tools.md +++ b/docs/guide_tools.md @@ -11,9 +11,9 @@ The AI's ability to interact with the filesystem is mediated by a three-layer se ### Global State ```python -_allowed_paths: set[Path] = set() # Explicit file allowlist (resolved absolutes) -_base_dirs: set[Path] = set() # Directory roots for containment checks -_primary_base_dir: Path | None = None # Used for resolving relative paths +_allowed_paths: set[Path] = set() # Explicit file allowlist (resolved absolutes) +_base_dirs: set[Path] = set() # Directory roots for containment checks +_primary_base_dir: Path | None = None # Used for resolving relative paths 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 | |---|---|---| | `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. | | `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) | @@ -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: ```python def py_get_skeleton(path: str) -> str: - p, err = _resolve_and_check(path) - if err: return err - 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}" - from file_cache import ASTParser - code = p.read_text(encoding="utf-8") - parser = ASTParser("python") - return parser.get_skeleton(code) + p, err = _resolve_and_check(path) + if err: return err + 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}" + from file_cache import ASTParser + code = p.read_text(encoding="utf-8") + parser = ASTParser("python") + return parser.get_skeleton(code) ``` **AST-based write tools** use stdlib `ast` (not tree-sitter) to locate symbols, then delegate to `set_file_slice`: ```python def py_update_definition(path: str, name: str, new_content: str) -> str: - p, err = _resolve_and_check(path) - if err: return err - code = p.read_text(encoding="utf-8").lstrip(chr(0xFEFF)) # Strip BOM - tree = ast.parse(code) - node = _get_symbol_node(tree, name) # Walks AST for matching node - if not node: return f"ERROR: could not find definition '{name}'" - start = getattr(node, "lineno") - end = getattr(node, "end_lineno") - return set_file_slice(path, start, end, new_content) + p, err = _resolve_and_check(path) + if err: return err + code = p.read_text(encoding="utf-8").lstrip(chr(0xFEFF)) # Strip BOM + tree = ast.parse(code) + node = _get_symbol_node(tree, name) # Walks AST for matching node + if not node: return f"ERROR: could not find definition '{name}'" + start = getattr(node, "lineno") + end = getattr(node, "end_lineno") + 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. @@ -200,19 +200,19 @@ Tools can be executed concurrently via `async_dispatch`: ```python async def async_dispatch(tool_name: str, tool_input: dict[str, Any]) -> str: - """Dispatch an MCP tool call asynchronously.""" - return await asyncio.to_thread(dispatch, tool_name, tool_input) + """Dispatch an MCP tool call asynchronously.""" + 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: ```python async def _execute_tool_calls_concurrently(calls, base_dir, ...): - tasks = [] - for fc in calls: - tasks.append(_execute_single_tool_call_async(name, args, ...)) - results = await asyncio.gather(*tasks) - return results + tasks = [] + for fc in calls: + tasks.append(_execute_single_tool_call_async(name, args, ...)) + results = await asyncio.gather(*tasks) + return results ``` 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 class HookServerInstance(ThreadingHTTPServer): - app: Any # Reference to main App instance + app: Any # Reference to main App instance class HookHandler(BaseHTTPRequestHandler): - # Accesses self.server.app for all state + # Accesses self.server.app for all state class HookServer: - app: Any - port: int = 8999 - server: HookServerInstance | None - thread: threading.Thread | None + app: Any + port: int = 8999 + server: HookServerInstance | 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. @@ -274,20 +274,20 @@ This ensures all state reads happen on the GUI main thread during `_process_pend ```python { - "mma_status": str, # "idle" | "planning" | "executing" | "done" - "ai_status": str, # "idle" | "sending..." | etc. - "active_tier": str | None, - "active_track": str, # Track ID or raw value - "active_tickets": list, # Serialized ticket dicts - "mma_step_mode": bool, - "pending_tool_approval": bool, # _pending_ask_dialog - "pending_mma_step_approval": bool, # _pending_mma_approval is not None - "pending_mma_spawn_approval": bool, # _pending_mma_spawn is not None - "pending_approval": bool, # Backward compat: step OR tool - "pending_spawn": bool, # Alias for spawn approval - "tracks": list, - "proposed_tracks": list, - "mma_streams": dict, # {stream_id: output_text} + "mma_status": str, # "idle" | "planning" | "executing" | "done" + "ai_status": str, # "idle" | "sending..." | etc. + "active_tier": str | None, + "active_track": str, # Track ID or raw value + "active_tickets": list, # Serialized ticket dicts + "mma_step_mode": bool, + "pending_tool_approval": bool, # _pending_ask_dialog + "pending_mma_step_approval": bool, # _pending_mma_approval is not None + "pending_mma_spawn_approval": bool, # _pending_mma_spawn is not None + "pending_approval": bool, # Backward compat: step OR tool + "pending_spawn": bool, # Alias for spawn approval + "tracks": list, + "proposed_tracks": list, + "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 { - "thinking": bool, # ai_status in ["sending...", "running powershell..."] - "live": bool, # ai_status in ["running powershell...", "fetching url...", ...] - "prior": bool, # app.is_viewing_prior_session + "thinking": bool, # ai_status in ["sending...", "running powershell..."] + "live": bool, # ai_status in ["running powershell...", "fetching url...", ...] + "prior": bool, # app.is_viewing_prior_session } ``` @@ -340,7 +340,7 @@ The counterpart `/api/ask/respond`: ```python 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 @@ -400,21 +400,21 @@ Tool calls are executed concurrently within a single AI turn using `asyncio.gath ```python async def async_dispatch(tool_name: str, tool_input: dict[str, Any]) -> str: - """ - Dispatch an MCP tool call by name asynchronously. - Returns the result as a string. - """ - # Run blocking I/O bound tools in a thread to allow parallel execution - return await asyncio.to_thread(dispatch, tool_name, tool_input) + """ + Dispatch an MCP tool call by name asynchronously. + Returns the result as a string. + """ + # Run blocking I/O bound tools in a thread to allow parallel execution + 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()`: ```python results = await asyncio.gather( - async_dispatch("read_file", {"path": "src/module_a.py"}), - async_dispatch("read_file", {"path": "src/module_b.py"}), - async_dispatch("get_file_summary", {"path": "src/module_c.py"}), + async_dispatch("read_file", {"path": "src/module_a.py"}), + async_dispatch("read_file", {"path": "src/module_b.py"}), + async_dispatch("get_file_summary", {"path": "src/module_c.py"}), ) ``` @@ -453,13 +453,13 @@ Summary: ``` logs/sessions// - comms.log # JSON-L: every API interaction (direction, kind, payload) - toolcalls.log # Markdown: sequential tool invocation records - apihooks.log # API hook invocations - clicalls.log # JSON-L: CLI subprocess details (command, stdin, stdout, stderr, latency) + comms.log # JSON-L: every API interaction (direction, kind, payload) + toolcalls.log # Markdown: sequential tool invocation records + apihooks.log # API hook invocations + clicalls.log # JSON-L: CLI subprocess details (command, stdin, stdout, stderr, latency) scripts/generated/ - _.ps1 # Each AI-generated PowerShell script, preserved in order + _.ps1 # Each AI-generated PowerShell script, preserved in order ``` ### Logging Functions