Replace run_id with flow_id as the primary grouping concept (one flow =
one user analysis intent spanning scan + pipeline + portfolio). Reports
are now written as {timestamp}_{name}.json so load methods always return
the latest version by lexicographic sort, eliminating the latest.json
pointer pattern for new flows.
Key changes:
- report_paths.py: add generate_flow_id(), ts_now() (ms precision),
flow_id kwarg on all path helpers; keep run_id / pointer helpers for
backward compatibility
- ReportStore: dual-mode save/load — flow_id uses timestamped layout,
run_id uses legacy runs/{id}/ layout with latest.json
- MongoReportStore: add flow_id field and index; run_id stays for compat
- DualReportStore: expose flow_id property
- store_factory: accept flow_id as primary param, run_id as alias
- runs.py / langgraph_engine.py: generate and thread flow_id through all
trigger endpoints and run methods
- Tests: add flow_id coverage for all layers; 905 tests pass
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: introduce flow_id with timestamp-based report versioning
Replace run_id with flow_id as the primary grouping concept (one flow =
one user analysis intent spanning scan + pipeline + portfolio). Reports
are now written as {timestamp}_{name}.json so load methods always return
the latest version by lexicographic sort, eliminating the latest.json
pointer pattern for new flows.
Key changes:
- report_paths.py: add generate_flow_id(), ts_now() (ms precision),
flow_id kwarg on all path helpers; keep run_id / pointer helpers for
backward compatibility
- ReportStore: dual-mode save/load — flow_id uses timestamped layout,
run_id uses legacy runs/{id}/ layout with latest.json
- MongoReportStore: add flow_id field and index; run_id stays for compat
- DualReportStore: expose flow_id property
- store_factory: accept flow_id as primary param, run_id as alias
- runs.py / langgraph_engine.py: generate and thread flow_id through all
trigger endpoints and run methods
- Tests: add flow_id coverage for all layers; 905 tests pass
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: PM brain upgrade — macro/micro summary agents, memory split, forensic dashboard
Replaces the PM's raw-JSON context (~6,800 tokens on deep_think) with a
MAP-REDUCE compression layer using two parallel mid_think summary agents,
achieving ~70% cost reduction at the PM tier.
Architecture:
- MacroMemory: new regime-level memory class (MongoDB/JSON, separate from
per-ticker reflexion memory) with record_macro_state/build_macro_context
- ReflexionMemory: extended with collection_name param to isolate
micro_reflexion from the pipeline reflexion collection (with distinct
local JSON fallback path to prevent file collision)
- Macro_Summary_Agent (mid_think): compresses scan_summary into a 1-page
regime brief with memory injection; sentinel guard prevents LLM call on
empty/error scan data ("NO DATA AVAILABLE - ABORT MACRO")
- Micro_Summary_Agent (mid_think): compresses holding_reviews + candidates
into a markdown table brief with per-ticker memory injection
- Portfolio graph: parallel fan-out (prioritize_candidates → macro_summary
‖ micro_summary → make_pm_decision) using _last_value reducers for safe
concurrent state writes (ADR-005 pattern)
- PM refactor: Pydantic PMDecisionSchema enforces Forensic Execution
Dashboard output (macro_regime, forensic_report, per-trade
macro_alignment/memory_note/position_sizing_logic); with_structured_output
as primary path, extract_json fallback for non-conforming providers
- PM sentinel handling: "NO DATA AVAILABLE" in macro_brief substituted
with actionable conservative guidance before LLM sees it
62 new unit tests across 4 test files covering all new components.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address code review — relaxed error guard, ticker_analyses, PM memory wiring
1. macro_summary_agent: relaxed error guard to only abort when scan_summary's
sole key is "error" (partial failures with real data are now processed)
2. micro_summary_agent: now reads ticker_analyses from state and enriches
the per-ticker table with trading graph analysis data
3. portfolio_graph: wires macro_memory and micro_memory to PM factory call
4. test_empty_state: updated test for new partial-failure behavior
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Optimized `get_completed_reports_count` to iterate over active report
sections directly instead of checking all possible sections, improving
performance.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: aguzererler <6199053+aguzererler@users.noreply.github.com>
* feat: introduce flow_id with timestamp-based report versioning
Replace run_id with flow_id as the primary grouping concept (one flow =
one user analysis intent spanning scan + pipeline + portfolio). Reports
are now written as {timestamp}_{name}.json so load methods always return
the latest version by lexicographic sort, eliminating the latest.json
pointer pattern for new flows.
Key changes:
- report_paths.py: add generate_flow_id(), ts_now() (ms precision),
flow_id kwarg on all path helpers; keep run_id / pointer helpers for
backward compatibility
- ReportStore: dual-mode save/load — flow_id uses timestamped layout,
run_id uses legacy runs/{id}/ layout with latest.json
- MongoReportStore: add flow_id field and index; run_id stays for compat
- DualReportStore: expose flow_id property
- store_factory: accept flow_id as primary param, run_id as alias
- runs.py / langgraph_engine.py: generate and thread flow_id through all
trigger endpoints and run methods
- Tests: add flow_id coverage for all layers; 905 tests pass
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: finalise storage layout, run history loading & phase-level re-run
Storage / persistence
- flow_id (8-char hex) replaces run_id as the disk storage key; all
sub-phases of one auto run share the same flow_id directory
- Startup hydration: hydrate_runs_from_disk() rebuilds in-memory run
store from run_meta.json on server restart (events lazy-loaded)
WebSocket / run history fixes
- Lazy-load events from run_events.jsonl on first WS connect; fixes
blank terminal when clicking a historical run after restart
- Orphaned "running" runs (server restarted mid-run) auto-detected and
marked "failed" with partial events replayed correctly
Phase re-run fixes
- Analysts checkpoint: use any() instead of all() — Social Analyst is
optional; all() silently blocked checkpoint saves in typical runs
- Checkpoint lookup: pass original flow_id through rerun_params so
_date_root() resolves to the correct flow_id subdirectory
- Selective event filtering on re-run: preserves scan nodes and other
tickers; only removes stale events for the re-run phase+ticker
- Frontend graph now shows full auto-flow context during phase re-runs
Documentation
- ADR 018: canonical reference for storage layout, event schema,
WebSocket streaming flows, checkpoint structure, MongoDB vs local
- ADR 013 updated: reflects background-task + lazy-loading evolution
- ADR 015 marked superseded by ADR 018
- CLAUDE.md: AgentOS storage section + 4 new critical patterns
- CURRENT_STATE.md updated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: introduce flow_id with timestamp-based report versioning
Replace run_id with flow_id as the primary grouping concept (one flow =
one user analysis intent spanning scan + pipeline + portfolio). Reports
are now written as {timestamp}_{name}.json so load methods always return
the latest version by lexicographic sort, eliminating the latest.json
pointer pattern for new flows.
Key changes:
- report_paths.py: add generate_flow_id(), ts_now() (ms precision),
flow_id kwarg on all path helpers; keep run_id / pointer helpers for
backward compatibility
- ReportStore: dual-mode save/load — flow_id uses timestamped layout,
run_id uses legacy runs/{id}/ layout with latest.json
- MongoReportStore: add flow_id field and index; run_id stays for compat
- DualReportStore: expose flow_id property
- store_factory: accept flow_id as primary param, run_id as alias
- runs.py / langgraph_engine.py: generate and thread flow_id through all
trigger endpoints and run methods
- Tests: add flow_id coverage for all layers; 905 tests pass
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: load flow_id in FE to resume runs and fix max_tickers cap on continuation
- Add flow_id to RunParams interface and initial state
- loadRun() now restores flow_id + max_auto_tickers from history so the next
run continues in the same flow directory (Phase 1 scan skipped, already-done
tickers skipped via skip-if-exists logic)
- startRun() spreads flow_id into the request body when set, letting the backend
reuse the existing flow directory instead of generating a fresh flow_id
- After each run, params.flow_id is updated from the response so subsequent
runs automatically continue from the same flow
- max_auto_tickers restored from run.params.max_tickers ensures the ticker cap
matches the original run; scan_tickers[:max_t] on the backend then limits
the Phase 2 queue to the user's setting even when the existing scan has more
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(mongo): fast-fail timeout + lazy ensure_indexes to avoid 30s block on fallback
MongoClient previously used pymongo's 30-second serverSelectionTimeoutMS default,
causing store_factory to hang for 30s before falling back to the filesystem when
Atlas is unreachable. Also, ensure_indexes() was called eagerly in __init__,
making every store construction attempt block on a live network call.
- Set serverSelectionTimeoutMS=5_000 so fallback is triggered in ≤5s
- Move ensure_indexes() call out of __init__ — indexes are now created lazily
on the first _save() call via a guarded self._indexes_ensured flag
- ensure_indexes() is still idempotent and safe to call explicitly in tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(store): wrap all DualReportStore mongo calls in _try_mongo() for graceful degradation
Any MongoDB exception (SSL error, ServerSelectionTimeout, auth failure) was
propagating uncaught through DualReportStore and crashing the run. Reads
would return an error instead of falling back to local, and writes would
abort mid-run without saving anything.
Introduce a single _try_mongo(fn, default) helper that:
- Executes the Mongo callable
- Catches *any* exception, logs it as WARNING with type + message
- Returns the default value so the caller continues with local-only data
Pattern per method:
writes → try mongo (fire-and-forget); always return local result
reads → try mongo first; fall back to local on None or exception
lists → try mongo; fall back to local on empty/None
Runs now complete successfully even when Atlas is unreachable or returns SSL
errors. MongoDB sync resumes automatically once connectivity is restored.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(observability): non-blocking MongoDB inserts + 5s timeout in RunLogger
Every LLM and tool callback called _append() which synchronously called
insert_one() against MongoDB. When Atlas was unreachable this blocked the
entire LangGraph run for pymongo's 30-second default timeout per event,
effectively serializing all agent work behind MongoDB retries.
Two fixes:
1. serverSelectionTimeoutMS=5_000 on the RunLogger's MongoClient — consistent
with the same fix applied to MongoReportStore.
2. MongoDB inserts are now fire-and-forget via daemon threads — _append() spawns
a Thread(target=_insert, daemon=True) and returns immediately. LLM callbacks
and tool events are never delayed by MongoDB connectivity issues.
Failures are still reported via WARNING log from the background thread.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* revert(observability): restore synchronous MongoDB inserts in RunLogger
Root cause was an IP whitelist issue on Atlas causing SSL failures, not
insert volume. The background-thread approach added unnecessary complexity.
The 5s serverSelectionTimeoutMS is retained as a defensive safeguard.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Feature 1 - Configurable max_auto_tickers:
- Add max_auto_tickers config key (default 10) with TRADINGAGENTS_MAX_AUTO_TICKERS env override
- Macro synthesis agent accepts max_scan_tickers param, injects exact count into LLM prompt
- ScannerGraph passes config value to create_macro_synthesis()
- Backend engine applies safety cap on scan candidates (portfolio holdings always included)
- Frontend adds Max Tickers number input in params panel, sends max_tickers in auto run body
Feature 2 - Run persistence + phase-level node re-run:
- 2A: ReportStore + MongoReportStore gain save/load_run_meta, save/load_run_events,
list_run_metas methods; runs.py persists to disk in finally block; startup hydration
restores historical runs; lazy event loading on GET /{run_id}
- 2B: Analysts + trader checkpoint save/load methods in both stores; engine saves
checkpoints after pipeline completion alongside complete_report.json
- 2C: GraphSetup gains build_debate_subgraph() and build_risk_subgraph() for partial
re-runs; TradingAgentsGraph exposes debate_graph/risk_graph as lazy properties;
NODE_TO_PHASE mapping + run_pipeline_from_phase() engine method;
POST /api/run/rerun-node endpoint with _append_and_store helper
- 2D: Frontend history popover (loads GET /api/run/, sorts by created_at, click to load);
triggerNodeRerun() calls rerun-node endpoint; handleNodeRerun uses phase-level
re-run when active run is loaded
All 890 existing tests pass (10 skipped).
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove the potential DoS and code-execution vulnerability by replacing `ast.literal_eval(tool_call)` with `json.loads` and `extract_json` in `cli/main.py`. Ensures strict JSON parsing without breaking tests or relying on unsafe structures.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: aguzererler <6199053+aguzererler@users.noreply.github.com>
* Initial plan
* feat: include portfolio holdings in auto mode pipeline analysis
In run_auto (both AgentOS and CLI), Phase 2 now loads current portfolio
holdings and merges their tickers with scan candidates before running
the per-ticker pipeline. This ensures the portfolio manager has fresh
analysis for both new opportunities and existing positions.
Key changes:
- macro_bridge.py: add candidates_from_holdings() factory
- langgraph_engine.py run_auto: merge holding tickers with scan tickers
- cli/main.py auto: load holdings, create StockCandidates, pass to run_pipeline
- cli/main.py run_pipeline: accept optional holdings_candidates parameter
- 9 new unit tests covering holdings inclusion, dedup, and graceful fallback
Co-authored-by: aguzererler <6199053+aguzererler@users.noreply.github.com>
Agent-Logs-Url: https://github.com/aguzererler/TradingAgents/sessions/53065a07-d9f8-47be-9956-0eb4ee8c87da
* fix: normalize ticker case in dedup and clarify count display
Address code review feedback:
- Use .upper() for case-insensitive ticker comparison in run_pipeline
- Display accurate filtered scan count instead of raw candidate count
Co-authored-by: aguzererler <6199053+aguzererler@users.noreply.github.com>
Agent-Logs-Url: https://github.com/aguzererler/TradingAgents/sessions/53065a07-d9f8-47be-9956-0eb4ee8c87da
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: aguzererler <6199053+aguzererler@users.noreply.github.com>
## Summary
- Adds `smart_money_scanner` as a new Phase 1b node that runs sequentially after `sector_scanner`, surfacing institutional footprints via Finviz screeners
- Introduces the **Golden Overlap** strategy in `macro_synthesis`: stocks confirmed by both top-down macro themes and bottom-up Finviz signals are labelled high-conviction
- Fixes model-name badge overflow in AgentGraph (long model IDs like OpenRouter paths were visually spilling into adjacent nodes)
- Completes all documentation: ADR-014, dataflow, architecture, components, glossary, current-state
## Key Decisions (see ADR-014)
- 3 zero-parameter tools (`get_insider_buying_stocks`, `get_unusual_volume_stocks`, `get_breakout_accumulation_stocks`) instead of 1 parameterised tool — prevents LLM hallucinations on string args
- Sequential after `sector_scanner` (not parallel fan-out) — gives access to `sector_performance_report` context and avoids `MAX_TOOL_ROUNDS=5` truncation in market_movers_scanner
- Graceful fallback: `_run_finviz_screen()` catches all exceptions and returns an error string — pipeline never hard-fails on web-scraper failure
- `breakout_accumulation` (52-wk high + 2x vol = O'Neil CAN SLIM institutional signal) replaces `oversold_bounces` (RSI<30 = retail contrarian, not smart money)
## Test Plan
- [x] 6 new mocked tests in `tests/unit/test_scanner_mocked.py` (happy path, empty DF, exception, sort order)
- [x] Fixed `tests/unit/test_scanner_graph.py` — added `smart_money_scanner` mock to compilation test
- [x] 2 pre-existing test failures excluded (verified baseline before changes)
- [x] AgentGraph badge: visually verified truncation with long OpenRouter model identifiers
🤖 Generated with [Claude Code](https://claude.com/claude-code)
* feat(ui): scoped graph nodes per ticker + MockEngine for LLM-free UI testing
## Summary
Adds a MockEngine that streams scripted agent events with zero real LLM calls,
enabling full UI testing (graph, terminal, drawer, metrics) without API keys or
network. Also fixes the ReactFlow graph so that each ticker/identifier gets its
own visual node — previously an auto run with 5 tickers collapsed all pipelines
into the same node IDs, overwriting each other.
## Changes
- **MockEngine** (`agent_os/backend/services/mock_engine.py`): new class that
generates realistic scripted events for pipeline, scan, and auto run types.
Supports configurable speed divisor (1× realistic → 10× instant). Auto mock
accepts a `tickers` list for multi-ticker runs.
- **POST /api/run/mock** (`runs.py`): new endpoint wiring MockEngine into the
BackgroundTasks + store pattern identical to real run endpoints.
- **WebSocket routing** (`websocket.py`): added `mock` run-type branch so the
WS executor path also dispatches to MockEngine when the background task hasn't
started yet.
- **LangGraphEngine** (`langgraph_engine.py`): added `_run_identifiers` dict to
track ticker/MARKET/portfolio_id per run; all emitted events now carry an
`identifier` field so the frontend can scope them.
- **AgentGraph.tsx**: ReactFlow nodes now keyed by `node_id:identifier` (e.g.
`news_analyst:AAPL`, `news_analyst:NVDA`). Edges scoped to same identifier.
`onNodeClick` passes raw `node_id` + `identifier` separately so the event
drawer can filter without parsing the scoped key.
- **Dashboard.tsx**: Mock button + type/speed controls added. `openNodeDetail`
accepts identifier; `NodeEventsDetail` filters by both `node_id` and
`identifier`. Comma-separated ticker input for mock auto runs (e.g.
`AAPL,NVDA,TSLA`).
- **useAgentStream.ts**: `AgentEvent` interface extended with `identifier?`
field.
## Decision Context
- Scoped node ID format chosen as `node_id:identifier` (colon separator) rather
than embedding identifier in the agent display name — keeps node labels clean
and identifier visible as a coloured badge, not label text.
- Raw `node_id` and `identifier` stored separately in `node.data` so the drawer
filtering (`events.filter(e => e.node_id === nodeId && e.identifier === id)`)
does not need to parse/split the scoped key.
- Parent edges are scoped to the same identifier as the child, assuming intra-
ticker chains. Cross-run topology edges (e.g. scan → pipeline) are implicit
via log events, not ReactFlow edges.
- MockEngine uses `asyncio.sleep` with a speed divisor — higher speed values
give faster replays for rapid iteration during UI development.
## Considerations for Future Agents
- Re-run button on graph nodes already uses `identifier` to dispatch
`startRun('pipeline', { ticker: identifier })` or `startRun('scan')` — no
further changes needed for per-node re-runs to be correctly scoped.
- The `_run_identifiers` dict in LangGraphEngine is keyed by `run_id`; it is
cleaned up after each run. If parallel runs are ever supported per engine
instance, this dict handles them correctly already.
- For run_auto, each sub-run (scan, per-ticker pipeline) calls its own
`run_scan`/`run_pipeline` which sets `_run_identifiers[run_id]`. The outer
`run_auto` does not set it — this is intentional.
- `uv.lock` changes reflect dependency tree after Chainlit removal in the
previous commit; no new runtime dependencies were added by this PR.
---
🤖 Commit Agent | Session: mock-engine + scoped-graph-nodes
* feat(graph): two-phase column layout — scan top, ticker columns below
## Summary
Redesigns the ReactFlow graph layout engine so scan nodes form a centred funnel
at the top and each ticker gets its own vertical column below, matching the
agreed design. Ticker header cards (bold ticker symbol + pulse dot + progress
counter) act as column anchors; agent cards stack beneath each one. Fan-out
dashed edges connect macro_synthesis → each ticker header.
## Changes
- SCAN phase: geopolitical/market-movers/sector scanners placed on the same
horizontal row at x = [0, COL_WIDTH, 2×COL_WIDTH] (aligns with first 3
ticker columns); industry_deep_dive and macro_synthesis centered below.
- TICKER columns: new identifiers get a TickerHeaderNode at tickerStartY;
agent nodes stack beneath using column-based parent tracking
(header → agent0 → agent1 → …) independent of evt.parent_node_id.
- TickerHeaderNode: wide card, bold ticker symbol, animated pulse status dot,
completedCount/agentCount counter updated live as results arrive.
- Tool nodes (node_id starts with "tool_") skipped from graph — visible in
terminal/drawer, not cluttering the column layout.
- Portfolio nodes centred below all ticker columns.
- Layout state extracted into LayoutState ref + freshLayout() for clean resets.
- Node labels use toLabel() (snake_case → Title Case).
- Metrics row shows total tokens (in+out) instead of just latency.
## Decision Context
- Column-based parent edges chosen over evt.parent_node_id because mock engine
emits parent_node_id="start" for all agents; column ordering is reliable.
- Scan phase X positions reuse COL_WIDTH so phase-1 scanners visually align
above first three ticker columns — no arbitrary magic numbers.
- Tool nodes removed from graph (not hidden) — they add noise to column layout
with no actionable meaning; the drawer already shows them per node.
## Considerations for Future Agents
- identifierLastNode tracks scoped ID of previous agent per ticker column —
used for sequential edge chaining; do not remove without replacing edge logic.
- tickerStartY is set once on first ticker arrival; subsequent tickers share
the same Y baseline — only colCount and identifierAgentRow differ per ticker.
- TickerHeaderNode clicks pass node_id='header' + identifier to onNodeClick;
Dashboard NodeEventsDetail filters all events by identifier when node_id is
'header' (shows the full ticker run timeline in the drawer).
---
🤖 Commit Agent | Session: two-phase column graph layout
- ReportStore.clear_portfolio_stage(date, portfolio_id): deletes pm_decision
(.json + .md) and execution_result files for a given date/portfolio
- DELETE /api/run/portfolio-stage endpoint: calls clear_portfolio_stage
and returns list of deleted files
- Dashboard: 'Reset Decision' button calls the endpoint, then user can
run Auto to re-run Phase 3 from scratch while skipping Phase 1 & 2
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a 'Force re-run' checkbox that passes force=True to the backend,
bypassing all date-based skip checks (scan, pipeline, portfolio, execution).
Also fixes auto run: ticker is not required (scan discovers tickers),
portfolio_id is the correct required field instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The scan summary never contained a 'prices' key, so trade execution always
ran with an empty prices dict. Now prices are fetched via yfinance:
- run_trade_execution: fetches prices for all tickers in the PM decision
(sells/buys/holds) when prices arg is empty
- run_auto resume path: fetches prices for decision tickers instead of
reading from scan data
- run_portfolio: fetches prices for current holdings + scan candidates
before invoking the portfolio graph
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TRADINGAGENTS_REPORTS_DIR now controls where all reports land (scans,
analysis, portfolio artifacts). Both report_paths.REPORTS_ROOT and
ReportStore.data_dir read from the same env var so the entire
reports/daily/{date}/... tree is rooted at one configurable location.
PORTFOLIO_DATA_DIR still works as a portfolio-specific override.
Falls back to "reports" (relative to CWD) when neither is set.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PortfolioRepository expects its own portfolio config (with data_dir),
not DEFAULT_CONFIG. Passing config=self.config caused a KeyError because
DEFAULT_CONFIG has no data_dir key. Removing the argument lets the
repository call get_portfolio_config() which provides the correct defaults.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extend run_portfolio to save Holding Reviews, Risk Metrics, PM Decision,
and Execution Result from final state
- Add run_trade_execution method for resuming trade execution from a saved
PM decision without re-running the full portfolio graph
- Update run_auto skip logic to check for execution result (not just decision)
and resume from saved decision when available
- Gitignore uv.lock and untrack it from version control
Co-Authored-By: Oz <oz-agent@warp.dev>