Files
manual_slop/docs/guide_architecture.md

7.0 KiB

Guide: Architecture

Overview of the package design, state management, and code-path layout.


The purpose of this software is to alleviate the pain points of using AI as a local co-pilot by encapsulating the workflow into a resilient, strictly controlled state machine. It manages context generation, API throttling, human-in-the-loop tool execution, and session-long logging.

There are two primary state boundaries used:

  • The GUI State (Main Thread, Retained-Mode via Dear PyGui)
  • The AI State (Daemon Thread, stateless execution loop)

All synchronization between these boundaries is managed via lock-protected queues and events.

Code Paths

Lifetime & Application Boot

The application lifetime is localized within App.run in gui_legacy.py.

  1. init parses the global config.toml (which sets the active provider, theme, and project paths).
  2. It immediately hands off to project_manager.py to deserialize the active .toml which hydrates the session's files, discussion histories, and prompts.
  3. Dear PyGui's dpg contexts are bootstrapped with docking_viewport=True, allowing individual GUI panels to exist as native OS windows.
  4. The main thread enters a blocking while dpg.is_dearpygui_running() render loop.
  5. On shutdown (clean exit), it performs a dual-flush: _flush_to_project() commits the UI state back to the .toml, and _flush_to_config() commits the global state to config.toml. The viewport layout is automatically serialized to dpg_layout.ini.

Context Shaping & Aggregation

Before making a call to an AI Provider, the current state of the workspace is resolved into a dense Markdown representation. This occurs inside aggregate.run.

If using the default workflow, aggregate.py hashes through the following process:

  1. Glob Resolution: Iterates through config["files"]["paths"] and unpacks any wildcards (e.g., src/**/*.rs) against the designated base_dir.
  2. File Item Build: build_file_items() reads each resolved file once, storing path, content, and mtime. This list is returned alongside the markdown so ai_client.py can use it for dynamic context refresh after tool calls without re-reading from disk.
  3. Markdown Generation: build_markdown_from_items() assembles the final <project>_00N.md string. By default (summary_only=False) it inlines full file contents. If summary_only=True, it delegates to summarize.build_summary_markdown() which uses AST-based heuristics to produce compact structural summaries instead.
  4. The Markdown file is persisted to disk (./md_gen/ by default) for auditing. run() returns a 3-tuple (markdown_str, output_path, file_items).

AI Communication & The Tool Loop

The communication model is unified under ai_client.py, which normalizes the Gemini and Anthropic SDKs into a singular interface send(md_content, user_message, base_dir, file_items).

The loop is defined as follows:

  1. Prompt Injection: The aggregated Markdown context and system prompt are injected. For Gemini, the system_instruction and tools are stored in an explicit cache via client.caches.create() with a 1-hour TTL; if cache creation fails (under minimum token threshold), it falls back to inline system_instruction. When context changes mid-session, the old cache is deleted and a new one is created. For Anthropic, the system prompt + context are sent as system= blocks with cache_control: ephemeral on the last chunk, and tools carry cache_control: ephemeral on the last tool definition.
  2. Execution Loop: A MAX_TOOL_ROUNDS (default 10) bounded loop begins. The tools list for Anthropic is built once per session and reused.
  3. The AI provider is polled.
  4. If the provider's stop_reason is tool_use:
    1. The loop parses the requested tool (either a read-only MCP tool or the destructive PowerShell tool).
    2. If PowerShell, it dispatches a blocking event to the Main Thread (see On Tool Execution & Concurrency).
    3. Once the last tool result in the batch is retrieved, the loop executes a Dynamic Refresh (_reread_file_items). Any files currently tracked by the project are pulled from disk fresh. The file_items variable is reassigned so subsequent tool rounds see the updated content.
    4. For Anthropic: the refreshed file contents are appended as a text block to the tool_results user message. For Gemini: the refreshed contents are appended to the last function response's output string. In both cases, the block is prefixed with [FILES UPDATED] / [SYSTEM: FILES UPDATED].
    5. On subsequent rounds, stale file-refresh blocks from previous turns are stripped from history to prevent token accumulation. For Gemini, old tool outputs exceeding _history_trunc_limit characters are also truncated.
  5. Once the model outputs standard text, the loop terminates and yields the string back to the GUI callback.

On Tool Execution & Concurrency

When the AI calls a safe MCP tool (like read_file or search_files), the daemon thread immediately executes it via mcp_client.py and returns the result.

However, when the AI requests run_powershell, the operation halts:

  1. The Daemon Thread instantiates a ConfirmDialog object containing the payload and calls .wait(). This blocks the thread on a threading.Event().
  2. The ConfirmDialog instance is safely placed in a _pending_dialog_lock.
  3. The Main Thread, during its next frame cycle, pops the dialog from the lock and renders an OS-level modal window using dpg.window(modal=True).
  4. The user can inspect the script, modify it in the text box, or reject it entirely.
  5. Upon the user clicking "Approve & Run", the main thread triggers the threading.Event, unblocking the Daemon Thread.
  6. The Daemon Thread passes the script to shell_runner.py, captures stdout, stderr, and exit_code, logs it to session_logger.py, and returns it to the LLM.

On Context History Pruning (Anthropic)

Because the Anthropic API requires sending the entire conversation history on every request, long sessions will inevitably hit the invalid_request_error: prompt is too long.

To solve this, ai_client.py implements an aggressive pruning algorithm:

  1. _strip_stale_file_refreshes: It recursively sweeps backward through the history dict and strips out large [FILES UPDATED] data blocks from old turns, preserving only the most recent snapshot.
  2. _trim_anthropic_history: If the estimated token count still exceeds _ANTHROPIC_MAX_PROMPT_TOKENS (~180,000), it slices off the oldest user/assistant message pairs from the beginning of the history array.
  3. The loop guarantees that at least the System prompt, Tool Definitions, and the final user prompt are preserved.

Session Persistence

All I/O bound session data is recorded sequentially. session_logger.py hooks into the execution loops and records:

  • logs/comms_.log: A JSON-L structured timeline of every raw payload sent/received.
  • logs/toolcalls_.log: A sequential markdown record detailing every AI tool invocation and its exact stdout result.
  • scripts/generated/: Every .ps1 script approved and executed by the shell runner is physically written to disk for version control transparency.