-
Notifications
You must be signed in to change notification settings - Fork 1
systems prompts
The prompt subsystem (src/prompts.rs + .hbs templates) is responsible for rendering the instructions that the external agent receives at the start of an analysis run. It uses Handlebars templates populated with domain data from the database.
| File | Purpose |
|---|---|
src/prompts.rs |
Prompt building logic (~920 lines): template rendering, explanation target extraction, finance-term detection. |
src/analysis_prompt.hbs |
Main analysis prompt template (single-equity, comparison, sector, watchlist, etc.). |
src/portfolio_analysis_prompt.hbs |
Portfolio-level analysis prompt template. |
src/explanation_prompt.hbs |
Explanation-pass prompt template for generating hover tooltips. |
Infi uses the handlebars Rust crate with include_str!() to embed templates at compile time. Each template receives a JSON context object and produces the final prompt string that is sent to the agent via ACP.
let template = include_str!("analysis_prompt.hbs");
let handlebars = Handlebars::new();
let prompt = handlebars.render_template(template, &json!({ ... }))?;No runtime file I/O is needed — templates are compiled into the binary.
build_prompt_for() selects the appropriate template based on the analysis intent:
pub fn build_prompt_for(analysis: &Analysis, run: &RunContext, db: &Database) -> Result<String> {
if analysis.intent == AnalysisIntent::Portfolio
&& let Some(portfolio_id) = analysis.portfolio_id.as_deref()
&& let Some(detail) = db.get_portfolio_detail(portfolio_id)?
{
return build_portfolio_analysis_prompt(run, &detail);
}
build_analysis_prompt(run)
}-
Portfolio intent →
portfolio_analysis_prompt.hbs(with portfolio snapshot data). -
All other intents →
analysis_prompt.hbs.
This is the primary prompt for single-equity, comparison, sector, watchlist, and general-research analyses. It is structured into several sections:
| Variable | Source |
|---|---|
{{analysis_id}} |
Run context |
{{run_id}} |
Run context |
{{user_prompt}} |
Run context |
{{agent_id}} |
Run context |
- Role — "You are Infi, a research analyst using ACP."
- Core policy — Research-only, no personalized financial advice. Use all available tools. Submit output through MCP tools, not markdown.
- Tool contract — Required reasoning fields are not optional. Out-of-range values reject calls.
-
Workflow — 9-step numbered workflow:
-
submit_research_planwith intent, decision criteria, planned checks. -
submit_methodology_noteonce. -
submit_entity_resolutionfor every entity. - Research using available tools.
-
submit_sourcefor every cited source. -
submit_metric_snapshotfor numeric claims. -
submit_structured_artifactfor comparison matrices, KPI grids, charts. -
submit_analysis_blockto build the report (with counter-thesis and uncertainty ledger). -
submit_final_stance→submit_projection→submit_decision_criterion_answer→finalize_analysis.
-
- Decision frame — Translate the request into 3-6 decision criteria.
- Required blocks — Always include thesis and risks; add financials, valuation, catalysts for single equity; peer_comparison for comparisons.
- Required artifacts — Specifies which structured artifact kinds to submit based on intent.
- Projection rules — Required for single_equity and compare_equities; must include bull/base/bear scenarios with probabilities summing to 1.0.
- Counter thesis — Required before directional stances; residual probability ≥ 0.10.
- Uncertainty — Blocking uncertainties cap stance confidence at 0.6.
-
Block quality — Specific formatting rules: concise titles, markdown body, evidence IDs,
[[N]]number highlighting. - Final stance — Required fields: stance, horizon, confidence, summary, key reasons, what would change.
- Freshness — Metrics must be within 12 months for directional stances.
Extends the main prompt for portfolio-level analysis. Additional template variables:
| Variable | Source |
|---|---|
{{portfolio.name}} |
Portfolio record |
{{portfolio.base_currency}} |
Portfolio record |
{{snapshot.as_of}} |
Most recent import batch |
{{snapshot.total_value}} |
Sum of holding market values |
{{snapshot.count}} |
Number of holdings |
{{holdings[]}} |
Array of {entity_id, symbol, market, name, quantity, price, market_value, weight_pct}
|
The portfolio prompt adds these steps beyond the base workflow:
-
Step 9:
submit_holding_reviewfor each holding ≥ 2% weight (keep/trim/add/watch/exit stance). -
Step 10:
submit_allocation_reviewonce (dimensions: asset class, sector, geography, currency). -
Step 11:
submit_portfolio_riskonce (factor exposures, macro sensitivities, tail risks). -
Step 12:
submit_portfolio_scenario_analysisonce (bull/base/bear portfolio outcomes, stress cases). -
Step 13:
submit_portfolio_expected_return_modelonce (weighted inputs, correlation assumptions). -
Step 14:
submit_rebalancing_suggestionif warranted (scenarios, not instructions).
- Rebalancing guidance must be framed as non-prescriptive scenarios, not instructions.
- Do not invent tax-regime or country-specific rules.
- The portfolio-level stance evaluates overall risk/return posture, not a single holding.
A second-pass prompt that runs after the main analysis to generate hover-to-explain tooltips. This is a separate ACP run with a different prompt.
| Variable | Source |
|---|---|
{{analysis_id}} |
Run context |
{{run_id}} |
Run context |
{{user_prompt}} |
Run context |
{{targets[]}} |
Extracted from the completed report |
Each target includes: target_type, target_key, display_name, metric_name, numeric_value, unit, as_of.
- Role — "You are Infi's explanation generator."
-
Instructions — For every target, call
submit_metric_explanationexactly once with:definition,meaning,value_interpretation,good_threshold,current_value_assessment. - Targets list — Rendered from the extracted targets.
- Style guide — Concise, plain language, reference actual values, no invented thresholds.
The agent calls only submit_metric_explanation and finalize_analysis — no other write tools.
explanation_targets_from_report() scans a completed AnalysisReport and extracts all explainable targets:
Every MetricSnapshot in the report becomes a target with target_type = "metric".
The report's block bodies are scanned for finance terms using a hardcoded dictionary of 20+ terms:
const FINANCE_TERMS: &[(&str, &[&str])] = &[
("pe", &["P/E", "P/E ratio", "price to earnings"]),
("casa", &["CASA", "CASA ratio"]),
("eps", &["EPS", "earnings per share"]),
("roe", &["ROE", "return on equity"]),
// ... 20+ more
];Each detected term becomes a target with target_type = "term".
Structured artifacts contribute targets for:
- Row labels with
metricorfactorkeys (e.g., KPI grid rows, ratio snapshot rows). - Column headers (each column label becomes a target).
Each projection contributes:
- The projected metric name.
- Each scenario (bull/base/bear) label.
- Holding review stance labels — each review's stance becomes a target.
- Allocation dimensions — each dimension in allocation reviews.
- Risk factor exposures — each factor in portfolio risks.
- Rebalancing row labels — each row in rebalancing suggestions.
Targets are deduplicated by (target_type, target_key) using a HashSet. Keys are normalized via normalize_explanation_key():
pub fn normalize_explanation_key(value: &str) -> String {
value.trim().to_ascii_lowercase()
.chars().map(|ch| if ch.is_ascii_alphanumeric() { ch } else { '_' })
.collect::<String>()
.split('_').filter(|p| !p.is_empty()).collect::<Vec<_>>().join("_")
}This matches the TypeScript implementation in frontend/src/features/report-viewer/explanation-utils.ts.
| File | Lines | Purpose |
|---|---|---|
src/prompts.rs |
~920 | All prompt building and target extraction logic. |
src/analysis_prompt.hbs |
~300 | Main analysis prompt template. |
src/portfolio_analysis_prompt.hbs |
~280 | Portfolio analysis prompt template. |
src/explanation_prompt.hbs |
~80 | Explanation pass prompt template. |
The prompts.rs test module includes:
-
explanation_targets_include_metrics_and_detected_terms()— Verifies that metrics and CASA/NIM terms are extracted from a fixture report. - Finance-term detection is tested against block body text containing specific terminology.
- ACP Integration — The rendered prompt is sent to the agent via ACP.
- Database — Report data is read from the DB to populate templates.