-
Notifications
You must be signed in to change notification settings - Fork 1
systems acp integration
The ACP (Agent Client Protocol) integration is the subsystem that spawns external coding agents, communicates with them over stdio, and persists their structured analysis output to the database. It is the core engine that turns a user's research question into a complete analysis report.
| File | Purpose |
|---|---|
src/infra/acp/mod.rs |
Module root; re-exports AgentCandidate, list_agent_candidates, resolve_agent_launch. |
src/infra/acp/agent_discovery.rs |
Agent registry: discovers and configures all supported agents. |
src/infra/acp/analysis_generator/mod.rs |
Re-exports worker types. |
src/infra/acp/analysis_generator/worker.rs |
Spawns the agent process, manages ACP session lifecycle, handles cancellation and timeouts. |
src/infra/acp/analysis_generator/client.rs |
Implements the ACP Client trait; processes streamed messages, tool calls, and progress events. |
src/infra/acp/analysis_mcp_server/mod.rs |
MCP server entry point; registers all tools and data-source providers. |
src/infra/acp/analysis_mcp_server/tool.rs |
Tool implementations (~3100 lines): validation, DB persistence, and finalization logic for every MCP tool. |
src/infra/acp/analysis_mcp_server/config.rs |
Server configuration: CLI args, env vars, run context loading. |
src/infra/progress.rs |
Progress event types streamed from worker to frontend. |
sequenceDiagram
participant UI as Frontend
participant CMD as Tauri Command
participant WK as ACP Worker
participant AG as External Agent
participant MCP as MCP Server
participant DB as SQLite
UI->>CMD: Start analysis
CMD->>WK: generate_with_acp(input)
WK->>AG: Spawn process (stdio)
WK->>AG: ACP initialize + new_session
WK->>AG: prompt(analysis_prompt)
loop Agent research loop
AG->>MCP: submit_research_plan
MCP->>DB: save plan
AG->>MCP: submit_entity_resolution
MCP->>DB: save entity
AG->>MCP: submit_source (×N)
MCP->>DB: save sources
AG->>MCP: submit_metric_snapshot (×N)
MCP->>DB: save metrics
AG->>MCP: submit_analysis_block (×N)
MCP->>DB: save blocks
AG->>MCP: submit_final_stance
MCP->>DB: save stance
AG->>MCP: finalize_analysis
MCP->>DB: validate + mark complete
end
WK-->>UI: Progress events (streamed)
WK-->>CMD: GenerateAnalysisResult
pub struct AgentCandidate {
pub id: String, // e.g. "codex", "claude"
pub label: String, // Display name
pub command: Option<String>, // Resolved binary path
pub args: Vec<String>, // Default launch arguments
pub available: bool, // Whether the binary was found
pub models: Vec<AgentModel>,
pub supports_model_override: bool,
}
pub struct AgentLaunch {
pub command: String,
pub args: Vec<String>,
pub env: Vec<(String, String)>,
}| Agent ID | Label | Discovery | Binary source |
|---|---|---|---|
codex |
Codex |
CODEX_ACP_BIN env or npx -y @zed-industries/codex-acp@latest
|
npx |
claude |
Claude |
CLAUDE_ACP_BIN env or npx -y @zed-industries/claude-code-acp
|
npx |
gemini |
Gemini |
GEMINI_ACP_BIN env or gemini --acp on PATH |
direct |
qwen |
Qwen Code |
QWEN_ACP_BIN env or qwen --acp on PATH |
direct |
mistral |
Mistral Vibe |
MISTRAL_ACP_BIN env or vibe-acp on PATH |
direct |
kimi |
Kimi |
KIMI_ACP_BIN env or kimi --acp on PATH |
direct |
opencode |
OpenCode |
OPENCODE_ACP_BIN env or opencode acp on PATH |
direct |
pi |
Pi |
PI_ACP_BIN env or npx -y pi-acp
|
npx |
custom |
Custom |
INFI_CUSTOM_AGENT env or settings |
user-defined |
resolve_agent_launch(agent_id, model_id):
- Looks up the agent by ID in the registry.
- Falls back to the first available agent if the requested one is not found.
- Validates model selection (rejects model override for agents that don't support it).
- Builds the
AgentLaunchwith model-specific args (e.g.,--model sonnetfor Claude,-c model=gpt-5.4for Codex).
Each agent struct implements the AgentDefinition trait, which provides candidate() and build_launch_for_model(). Model args are appended differently per agent — Codex uses -c model=X, Claude uses --model X, OpenCode prepends --model X before the subcommand.
-
find_bin(name)— Searches the system PATH viawhich. -
resolve_env_bin(path)— Resolves an env-var-provided path, checking if the file exists. - On Windows,
.cmd/.batfiles are wrapped withcmd /Cfor proper execution.
The worker runs on a detached OS thread with its own single-threaded Tokio runtime. This isolation prevents Tauri's async runtime from blocking on agent I/O.
pub async fn generate_with_acp(mut input: GenerateAnalysisInput) -> Result<GenerateAnalysisResult>Key behaviors:
-
Timeout: Default 1800 seconds (30 minutes). Wraps the inner work in
tokio::time::timeout. -
Cancellation: Uses a
CancellationToken+ RAIICancelOnDropguard. If the parent future is dropped, the token cancels and the child process is killed. -
Process management: Spawns the agent with
kill_on_drop(true),process_group(0)on Unix for clean group kills, andsuppress_windows_console_tokioon Windows. - Secret redaction: All source API keys are redacted from logs before they reach the frontend.
-
MCP server injection: The same Infi binary is re-invoked with
--analysis-mcp-serveras the MCP child process. Source keys are injected asINFI_SRC_KEY_<ID>environment variables. -
Context file: A temporary file containing the serialized
RunContextis passed to the MCP server via--analysis-context.
The worker loop:
- Spawns the agent process.
- Pipes stderr to a logging task (with secret redaction).
- Creates an
InfiClientand establishes the ACP connection. - Calls
initialize()→new_session()(with MCP server config) →prompt(). - Polls for finalization or agent exit every 200ms.
- Kills the process group on completion/cancellation/timeout.
InfiClient implements agent_client_protocol::Client. It processes four types of ACP events:
| ACP Event | Handler |
|---|---|
SessionUpdate::AgentMessageChunk |
Appends to message buffer, emits MessageDelta progress. |
SessionUpdate::AgentThoughtChunk |
Appends to thought buffer, emits ThoughtDelta progress. |
SessionUpdate::ToolCall |
Tracks pending tool calls, emits ToolCallStarted / ToolCallComplete. |
SessionUpdate::ToolCallUpdate |
Merges updates into pending calls, emits completion on terminal status. |
SessionUpdate::Plan |
Emits Plan progress with frontend-formatted entries. |
ext_method / ext_notification
|
Handles extension payloads for finalize_analysis and other Infi tools. |
The client tracks tool call names in a HashMap<tool_call_id, tool_name> to correlate started/completed events. It also maintains per-message-ID streaming lengths to compute deltas efficiently.
Permission requests from the agent are auto-approved (allow-once).
The MCP server runs as a child process of the agent. It uses the pmcp crate to expose tools over stdio:
pub async fn run_analysis_mcp_server() -> pmcp::Result<()>The server registers:
- 21 built-in tools for submitting analysis artifacts.
-
Data-source tools for each enabled provider (e.g.,
tavily_search,sec_edgar_lookup). Providers requiring API keys are skipped if no key is available.
pub struct ServerConfig {
pub run_context: Option<PathBuf>,
pub db_path: Option<PathBuf>,
pub source_keys: HashMap<String, String>,
}Loaded from CLI args (--analysis-context, --db-path) and environment variables (INFI_ANALYSIS_CONTEXT, INFI_DB_PATH, INFI_SRC_KEY_*). The load_context() method reads and deserializes the RunContext JSON file.
Each tool is a SimpleTool with a JSON schema, validation, and DB persistence. The full tool set:
| Tool | Purpose |
|---|---|
submit_research_plan |
Persists the agent's interpreted intent, decision criteria, and planned checks. |
submit_entity_resolution |
Resolves a ticker/company/ETF/sector entity with confidence score. |
submit_source |
Registers a source before citing it. Required for evidence ID validation. |
verify_source_accessibility |
HEAD/GET probe on a source URL; records OK/redirect/forbidden/dead status. |
submit_metric_snapshot |
Persists a numeric metric with value, unit, period, as-of date, source link. |
submit_metric_explanation |
Adds hover-tooltip explanations for metrics, terms, artifacts, projections. |
submit_structured_artifact |
Persists comparison matrices, KPI grids, charts, scenario matrices. |
submit_analysis_block |
Persists a report section (thesis, risks, financials, etc.). |
submit_final_stance |
Records the agent's directional stance with confidence and key reasons. |
submit_projection |
Persists bull/base/bear projections with probability validation (sum ≈ 1.0). |
submit_counter_thesis |
Records the steelman opposing case with residual probability ≥ 0.10. |
submit_uncertainty_ledger |
Logs unresolved questions; blocking entries cap stance confidence at 0.6. |
submit_methodology_note |
Records research approach, frameworks, data windows, limitations. |
submit_decision_criterion_answer |
Records per-criterion verdict matching the research plan. |
submit_holding_review |
Portfolio: per-holding stance (keep/trim/add/watch/exit). |
submit_allocation_review |
Portfolio: allocation breakdown by dimension. |
submit_portfolio_risk |
Portfolio: factor exposures, macro sensitivities, tail risks. |
submit_rebalancing_suggestion |
Portfolio: current vs. suggested weights with scenarios. |
submit_portfolio_scenario_analysis |
Portfolio: bull/base/bear outcomes with stress cases. |
submit_portfolio_expected_return_model |
Portfolio: weighted expected-return inputs. |
finalize_analysis |
Runs validate_finalization() and marks the run complete. |
Tools enforce strict contracts:
-
Confidence values: Must be in
[0.0, 1.0]— null or out-of-range rejects the call. - Evidence IDs: Must reference previously submitted sources — unknown IDs reject the call.
- Probability sums: Projection scenarios must sum to 1.0 within 0.02 tolerance.
-
Block quality: Analysis blocks with
kind = "thesis"that are too similar to existing blocks (Jaccard similarity > 0.6) are rejected as duplicates. - Required fields: Missing required fields produce descriptive validation errors, not silent defaults.
-
Directional stance gate:
bullishorbearishstances require a counter-thesis with residual probability ≥ 0.10; otherwise the tool rejects the call. -
Data freshness gate: Directional stances require all metrics to have
as_ofwithin 12 months; older metrics block finalization.
For each enabled data-source provider, the MCP server creates a thin wrapper tool:
fn create_source_tool(provider: &'static dyn SourceProvider, api_key: Option<String>) -> impl ToolHandlerThis delegates to provider.query() with the stored API key, exposing the provider's input_schema() as the tool's JSON schema.
The worker streams ProgressEventPayload events through a tokio::sync::mpsc::UnboundedSender:
| Event | Trigger |
|---|---|
Log(String) |
Stderr output, spawn messages, lifecycle events. |
MessageDelta { id, delta } |
Incremental agent message text. |
ThoughtDelta { id, delta } |
Incremental agent reasoning text. |
ToolCallStarted { id, title, kind } |
Agent began a tool call. |
ToolCallComplete { id, status, title, raw_input, raw_output } |
Tool call finished (completed/failed). |
Plan(FrontendPlan) |
Agent submitted a plan via ACP SessionUpdate::Plan. |
PlanSubmitted, SourceSubmitted, MetricSubmitted, etc. |
Milestone events when specific tools complete. |
Completed |
Finalization received. |
Error { message } |
Fatal error. |
The Tauri frontend subscribes to these events to render the live agent timeline.
| Error type | Meaning |
|---|---|
AcpCancelled |
User cancelled the run via the UI. |
AcpTimeout(secs) |
Agent exceeded the timeout (default 1800s). |
| ACP prompt failure | Agent failed during the prompt phase; error message extracted from nested JSON. |
| Missing finalization | Agent exited without calling finalize_analysis. |
- Database — All MCP tools persist data here.
- Data Sources — Providers are registered as MCP tools.
- Prompts — The rendered prompt is what the agent receives.