Skip to content

Latest commit

 

History

History
245 lines (191 loc) · 10.6 KB

File metadata and controls

245 lines (191 loc) · 10.6 KB

Design

Detailed architecture reference for Ho Launcher. For a concise overview see Architecture.md.

Tauri desktop app with React frontend and Rust backend.

Stack

Layer Technology
Shell Tauri
Frontend React, TypeScript, Vite, Tailwind CSS, DaisyUI
Backend Rust, Tokio
MCP SDK rmcp
LLM rig-core, graph-flow, toon-format
Script runtimes RustPython, Boa

Identifier: cool.thinkers.ho.

Directory Layout

Ho-ho/
├── backend/          # Rust/Tauri backend
│   ├── src/          # Application modules
│   ├── prompts/      # LLM prompt templates
│   └── capabilities/ # Tauri permission scopes
├── frontend/         # React/Vite frontend (app/, features/, shared/)
├── test/             # CLI E2E scripts and fixtures
├── build/            # Platform build scripts
├── scripts/          # Version and verify helpers
├── EULA/             # End-user license
└── docs/             # Documentation

Backend Modules

Module Path Responsibility
agent agent/ Graph-based agent run, rig tool loop, handoff, conversation store, pending_run, commands
domain domain/ Shared config types and validation (agent, mcp, model, skill, guardrail, budget, attachment, config_path)
runtime runtime/ Durable Run + RunEvent store, graph session persistence, Tauri commands
approvals approvals/ Persisted approval requests and resolution service
registry registry/ Split config files (models.json, mcp-servers.json, agent.json, skills.json, etc.)
guardrails guardrails/ Guardrail profile service
budget budget/ Token budget check and ledger
skills skills/ SKILL.md loader, registry service, MCP context injection
agent_os agent_os/ Platform capabilities for builtin MCP
app_platform app_platform/ Installed app discovery and launch
app_run app_run/ App discovery, execution, built-in App Launcher MCP
launcher launcher/ Tool suggestions, direct tool execution
llm llm/ rig transport, completion, health checks, TOON, prompts
mcp mcp/ MCP client service (stdio + HTTP + builtin), OAuth, client handler, task polling
cli cli/ Headless operator commands
script_run script_run/ Script generation + RustPython/Boa execution
settings settings/ Core settings.json, split registry merge (service.rs), field updaters, prompt service, registry access, EULA
system system/ Tray, shortcuts, OS-specific behavior
window window/ Window show/hide, positioning, click-through, DevTools overlay
init init/ App state init, async startup sequence
utils utils/ Logging, analytics, file context, tool scoping, json_store, path utils
dev_tools dev_tools/ DevTools toggle

Managed Tauri State

backend/src/init/state.rs:

  • MCPAppState, LLMAppState, ScriptRunState, AppRunState, SettingsState, AgentManager, ApprovalStore, GraphSessionStore, RunManager

Startup Sequence

backend/src/init/services.rs → setup_async_initialization:

  1. Load settings (merge split registry files) → emit settings events to frontend
  2. Setup windows, tray, global shortcut
  3. Start built-in App Launcher MCP HTTP server (port 1228)
  4. Initialize configured MCP servers; auto-connect if enabled
  5. Initialize LLM from settings models; health-check via tiered probe
  6. Trigger app discovery; register settings event listeners

Data Flow

Frontend (React)
    │ invoke()
    ▼
Tauri Commands (94 registered in `commands.rs`)
    │
    ├── launcher/     → MCP tool suggestions & execution
    ├── agent/        → graph-flow → rig agent → MCP tools
    ├── runtime/      → run_get, run_list, run_list_events
    ├── approvals/    → list_approvals
    ├── script_run/   → LLM script gen → embedded runtime
    ├── mcp/          → Server connect, OAuth, tool/resource/prompt calls, elicitation
    ├── llm/          → rig completions & health checks
    ├── app_run/      → OS app discovery & launch
    └── settings/     → JSON persistence + registry split files
    │
    │ emit events (run-event, launcher-progress, …)
    ▼
Frontend (runTimelineStore + event listeners)

Settings Persistence

Core settings.json: general, appearance, advanced, prompts, script_run, rules.

Split registry (atomic write via write_json_atomic):

File Contents
models.json LLM models and LLM settings
mcp-servers.json MCP servers and MCP settings
agent.json Agent settings (skillIds, guardrail limits)
skills.json Skill registry
skills-policy.json Skill loading mode
guardrails.json Guardrail profiles
token-budgets.json Token budget limits
approvals.json Approval request history

LLM models are flat objects:

{
  "id": "...",
  "name": "...",
  "provider": "openai",
  "baseUrl": "https://api.openai.com/v1",
  "model": "gpt-4o",
  "apiKey": "env:OPENAI_API_KEY",
  "enabled": true
}
  • Debug path: {project_root}/app-data/
  • Release path: OS config dir → thinkers/ho/
  • Run files: {app_data_dir}/runs/{runId}.json, {app_data_dir}/events/{runId}.jsonl
  • Conversations: {app_data_dir}/conversations/{convId}.json
  • Graph sessions: {app_data_dir}/sessions/{runId}.graph.json
  • Pending agent runs: {app_data_dir}/pending-runs/{runId}.json
  • Cancel / resume IPC: {app_data_dir}/cancel-requests/{runId}, {app_data_dir}/resume-requests/{runId}
  • Token ledger: {app_data_dir}/token-ledger.jsonl
  • App launcher cache: {app_data_dir}/app_cache.json
  • Application logs: {app_data_dir}/logs/ho.log.* (daily rotation)

Core UI settings live in settings.json. Registry data (models.json, mcp-servers.json, agent.json, etc.) is merged on load.

Agent Architecture

Graph-flow task pipeline (backend/src/agent/graph.rs):

Task Module Role
prepare_run tasks/prepare.rs Build preamble; load skills + MCP skill context
execute_agent tasks/execute.rs rig Agent + all MCP RigMcpTool + AgentApprovalHook + optional handoff
complete_run tasks/complete.rs Terminal run status and events

All scoped MCP tools are bound to the rig tool server. Rig tool names use {serverId}_{toolName} slugging (mcp/naming.rs).

Graph execution state persists across approval waits via GraphSessionStore. Runs enter RunStatus::Blocked while awaiting approval.

Run-Centric State

Observable state follows a single durable trace separate from orchestrator internals.

Layer Ho module Role SSOT?
Run record runtime/store.rs → runs/{runId}.json Status, input, timestamps Yes — durable run metadata
Run events events/{runId}.jsonl Append-only trace: tool.*, message.completed, run.* Yes — UI, audit, recovery
Graph checkpoint GraphSessionStore → sessions/{runId}.graph.json graph-flow task pointer + context for resume after approval Internal only
Pending run agent/pending_run.rs → pending-runs/{runId}.json Detached CLI worker payload Internal only
Run coordination AgentManager Cancel tokens + resume notifiers keyed by run_id Ephemeral only
UI projection runTimelineStore Subscribes to run-event; rebuilds from run_list_events Derived from events

Rules:

  1. Run + RunEvent JSONL are the only durable observable state.
  2. GraphSessionStore is a private orchestrator checkpoint keyed by run_id.
  3. All user-visible progress is emitted as run events; frontend listens on run-event.
  4. agent_get_session removed — use run_get + run_list_events.
  5. AgentManager holds coordination only (cancel, resume), not duplicated domain state.

LLM Layer

All provider HTTP goes through rig-core OpenAI-compatible client:

  • llm/openai_compatible.rs — resolve API key (including env: refs), build client
  • llm/completion.rs — complete, health helpers
  • llm/types/error.rs — typed LLMErrorCode, secret redaction

Launcher suggestions use local keyword search only. Agent mode uses native tool calling.

Prompt System

Shipped templates in backend/prompts/ (3-layer: preamble, *-system, *-user per workflow).

Category Runtime use User-customizable preamble
result_analysis Outcome formatting Yes
script_run Vibe script generation Yes
agent_planning Agent preamble only Yes
app_analysis App metadata batches No
system Global prefix for workflow system prompts No

Launcher suggestions use local keyword search only — no LLM prompt category.

Custom preamble overrides live in settings.json → prompts.customTemplates. One enabled override per customizable category.

Module Skeleton Template

Reference implementation: backend/src/approvals/. New feature modules follow this shape.

feature/
├── mod.rs           # pub re-exports only
├── commands.rs      # #[tauri::command] → service (thin handlers)
├── service.rs       # orchestration
├── store.rs         # persistence (when module owns files)
└── types.rs         # module-local types; import domain/* for shared config

Optional: tasks/, tools/ subdirs for complex features (agent).

Error layering

Layer Type Used in Maps to IPC
domain/error.rs AppError, validation domain, registry, services via BackendError::from
errors.rs BackendError, BackendResult commands, all IPC handlers JSON { message } to frontend
llm/types/error.rs LLMError, LLMErrorCode llm service internals BackendError::msg in llm commands
mcp/types MCPError mcp client/service BackendError or MCPResponse::error

Rules:

  • Feature code returns BackendResult<T> at the command boundary only
  • Services and stores use AppError or module-specific errors internally
  • Never expose internal error types across IPC; redact secrets in LLM errors

Layer rules

commands → service → store → domain → utils
  • domain/ never imports feature modules
  • One persistence writer per JSON file (RegistryStore for split config)
  • Internal errors (AppError, MCPError, LLMError) map to BackendError at IPC boundary only