Current Planner Graph¶
This reference documents the current planner subgraph as implemented in src/venturescope/planner/agent.py and driven by run_planner_step() in src/venturescope/planner/__init__.py.
Mermaid graph¶
flowchart TD
planner_start([START]) --> plan[plan]
plan -->|decision.action = search| search[search]
plan -->|decision.action = ask_user| ask_user[ask_user / interrupt]
plan -->|decision.action = reflect| reflect[reflect]
plan -->|decision.action = calculate| calculate[calculate]
plan -->|decision.action = finish| finish[finish]
search -->|last_observation present| observe[observe]
search -->|no hits or backend failure| plan
observe --> plan
calculate --> plan
ask_user --> observe_user[observe_user]
observe_user --> plan
reflect --> plan
finish --> planner_end([END])
Node roles¶
plan: increments iterations, handles early stop conditions, asks region/currency questions first, manages decomposition/calculation routing, and produces a structuredPlannerDecision.search: runs the configured search backend, refines queries, and either storeslast_observationor records a failed search attempt.observe: parses search results intoFieldEvidence, validates the evidence, and merges accepted values into the schema.calculate: runs the deterministic calculator for calculator-backed profiles such assubscription_recurring.ask_user: raises a LangGraphinterrupt()withfield, optionalcomponent,question,reasoning, andsuggested_answers.observe_user: parses the resumed user answer, supports multi-component extraction, validates it, and merges accepted values into the schema.reflect: appends a short reflection note to planner state.finish: returnsstatus="done"when the planner can stop cleanly, otherwisestatus="aborted"when iteration/attempt limits were hit with unresolved work.
Routing details¶
plan -> ask_user¶
plan_node() routes to ask_user when:
core.regionis still missing and region retries are not exhaustedcore.currencyis still missing and currency retries are not exhausted- acquisition logic selects a user question for a missing field or component
- the planner LLM chooses
ask_user - a search cap forces fallback from
searchtoask_user
plan -> search¶
plan_node() routes to search when:
- acquisition logic selects a search task
- the planner LLM chooses
search - a web-preferred field was incorrectly targeted with
ask_userbefore any search attempt, so the decision is redirected tosearch
plan -> calculate¶
plan_node() rewrites finish to calculate for calculator-backed profiles when raw inputs are complete enough but the deterministic calculator has not yet succeeded and the calculation-attempt cap has not been reached.
plan -> reflect¶
plan_node() routes to reflect when:
- the planner LLM explicitly chooses
reflect - a component-derived field needs decomposition-aware follow-up instead of a direct aggregate question
- a blocked calculator requires another reasoning pass before the planner can finish
plan -> finish¶
plan_node() routes to finish when:
- the planner was already aborted
max_itershas been exceeded- calculation attempts are exhausted for a calculator-backed profile
- all actionable raw inputs are collected and there are no open acquisition tasks
- ask-user retries for the same field hit the per-field cap
- the planner LLM returns invalid structured output and the planner falls back to finish
search -> observe | plan¶
route_after_search() sends the flow to:
observeiflast_observationis populatedplanif the search produced no hits or the backend failed
Outer-turn entry and resume¶
run_planner_step() drives the subgraph in two modes:
- bootstrap:
graph.invoke(initial_state(...), config=config) - resume after an interrupt:
graph.invoke(Command(resume=user_message), config=config)
The planner checkpoint thread is namespaced as {conversation_id}:planner via planner_thread_id().
Source of truth¶
- graph construction:
src/venturescope/planner/agent.py:2082 - planner node implementations:
src/venturescope/planner/agent.py - outer planner step wrapper:
src/venturescope/planner/__init__.py:94