Changes:
- Write TRADING_DAY_ID to runtime config after creating trading_day record in BaseAgent
- Fix datetime deprecation warnings by replacing datetime.utcnow() with datetime.now(timezone.utc)
- Add test for trading_day_id=None fallback path to verify runtime config lookup works correctly
This ensures trade tools can access trading_day_id from runtime config when not explicitly passed.
This commit implements Task 1 from the schema migration plan:
- Trade tools (buy/sell) now write to actions table instead of old positions table
- Added trading_day_id parameter to buy/sell functions
- Updated ContextInjector to inject trading_day_id
- Updated RuntimeConfigManager to include TRADING_DAY_ID in config
- Removed P&L calculation from trade functions (now done at trading_days level)
- Added tests verifying correct behavior with new schema
Changes:
- agent_tools/tool_trade.py: Modified _buy_impl and _sell_impl to write to actions table
- agent/context_injector.py: Added trading_day_id parameter and injection logic
- api/model_day_executor.py: Updated to read trading_day_id from runtime config
- api/runtime_manager.py: Added trading_day_id to config initialization
- tests/unit/test_trade_tools_new_schema.py: New tests for new schema compliance
All tests passing.
Critical fixes:
1. Fixed api/database.py import - use get_db_path() instead of non-existent get_database_path()
2. Fixed state management - use database queries instead of reading from position.jsonl file
3. Fixed action counting - track during trading loop execution instead of retroactively from conversation history
4. Completed integration test to verify P&L calculation works correctly
Changes:
- agent/base_agent/base_agent.py:
* Updated _get_current_portfolio_state() to query database via get_current_position_from_db()
* Added today_date and job_id parameters to method signature
* Count trade actions during trading loop instead of post-processing conversation history
* Removed obsolete action counting logic
- api/database.py:
* Fixed import to use get_db_path() from deployment_config
* Pass correct default database path "data/trading.db"
- tests/integration/test_agent_pnl_integration.py:
* Added proper mocks for dev mode and MCP client
* Mocked get_current_position_from_db to return test data
* Added comprehensive assertions to verify trading_day record fields
* Test now actually validates P&L calculation integration
Test results:
- All unit tests passing (252 passed)
- All P&L integration tests passing (8 passed)
- No regressions detected
This implements Task 5 from the daily P&L results API refactor plan, bringing
together P&L calculation and reasoning summary into the BaseAgent trading session.
Changes:
- Add DailyPnLCalculator and ReasoningSummarizer to BaseAgent.__init__
- Modify run_trading_session() to:
* Calculate P&L at start of day using current market prices
* Create trading_day record with P&L metrics
* Generate reasoning summary after trading using AI model
* Save final holdings to database
* Update trading_day with completion data (cash, portfolio value, summary, actions)
- Add helper methods:
* _get_current_prices() - Get market prices for P&L calculation
* _get_current_portfolio_state() - Read current state from position.jsonl
* _calculate_portfolio_value() - Calculate total portfolio value
Integration test verifies:
- P&L calculation components exist and are importable
- DailyPnLCalculator correctly calculates zero P&L on first day
- ReasoningSummarizer can be instantiated with AI model
This maintains backward compatibility with position.jsonl while adding
comprehensive database tracking for the new results API.
Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
- Implement ReasoningSummarizer class for generating 2-3 sentence AI summaries
- Add fallback to statistical summary when AI generation fails
- Format reasoning logs for summary prompt with truncation
- Handle empty reasoning logs with default message
- Add comprehensive unit tests with async mocking
Root cause: AI models were hallucinating signature/job_id/today_date values
and passing them in tool calls. The ContextInjector was checking
"if param not in request.args" before injecting, which failed when AI
provided (incorrect) values.
Fix: Always override context parameters, never trust AI-provided values.
Evidence from logs:
- ContextInjector had correct values (self.signature=gpt-5, job_id=6dabd9e6...)
- But AI was passing signature=None or hallucinated values like "fundamental-bot-v1"
- After injection, args showed the AI's (wrong) values, not the interceptor's
This ensures runtime context is ALWAYS injected regardless of what the AI sends.
Fixes #TBD
Log ContextInjector instance ID and attribute values at entry to __call__()
to diagnose why attributes appear as None during tool invocation despite
being set correctly during set_context().
This will reveal whether:
- Multiple ContextInjector instances exist
- Attributes are being overwritten/cleared
- Wrong instance is being invoked
Add instrumentation at component boundaries to trace where ContextInjector values become None:
- ModelDayExecutor: Log ContextInjector creation and set_context() invocation
- BaseAgent.set_context(): Log entry, client creation, tool reload, completion
- Includes object IDs to verify instance identity across boundaries
Part of systematic debugging investigation for issue #TBD.
Critical fixes for ContextInjector and database concurrency:
1. ContextInjector Not Working:
- Made set_context() async to reload tools after recreating MCP client
- Tools from old client (without interceptor) were still being used
- Now tools are reloaded from new client with interceptor active
- This ensures buy/sell calls properly receive injected parameters
2. Database Locking:
- Closed main connection before _write_results_to_db() opens new one
- SQLite doesn't handle concurrent write connections well
- Prevents "database is locked" error during position writes
Changes:
- agent/base_agent/base_agent.py:
- async def set_context() instead of def set_context()
- Added: self.tools = await self.client.get_tools()
- api/model_day_executor.py:
- await agent.set_context(context_injector)
- conn.close() before _write_results_to_db()
Root Cause:
When recreating the MCP client with tool_interceptors, the old tools
were still cached in self.tools and being passed to the AI agent.
The interceptor was never invoked, so job_id/signature/date were missing.
This commit migrates the system to database-only position storage,
eliminating file-based position.jsonl dependencies and fixing
ContextInjector initialization timing issues.
Key Changes:
1. ContextInjector Lifecycle Refactor:
- Remove ContextInjector creation from BaseAgent.__init__()
- Add BaseAgent.set_context() method for post-initialization injection
- Update ModelDayExecutor to create ContextInjector with correct trading day date
- Ensures ContextInjector receives actual trading date instead of init_date
- Includes session_id injection for proper database linking
2. Database Position Functions:
- Implement get_today_init_position_from_db() for querying previous positions
- Implement add_no_trade_record_to_db() for no-trade day handling
- Both functions query SQLite directly (positions + holdings tables)
- Handle first trading day case with initial cash return
- Include comprehensive error handling and logging
3. System Integration:
- Update get_agent_system_prompt() to use database queries
- Update _handle_trading_result() to write no-trade records to database
- Remove dependencies on position.jsonl file reading/writing
- Use deployment_config for automatic prod/dev database resolution
Data Flow:
- ModelDayExecutor creates runtime config and trading session
- Agent initialized without context
- ContextInjector created with (signature, date, job_id, session_id)
- Context injected via set_context()
- System prompt queries database for yesterday's position
- Trade tools write directly to database
- No-trade handler creates database records
Fixes:
- ContextInjector no longer receives None values
- No FileNotFoundError for missing position.jsonl files
- Database is single source of truth for position tracking
- Session linking maintained across all position records
Design: docs/plans/2025-02-11-database-position-tracking-design.md
Complete rewrite of position management in MCP trade tools:
**Trade Tools (agent_tools/tool_trade.py)**
- Replace file-based position.jsonl reads with SQLite queries
- Add get_current_position_from_db() to query positions and holdings tables
- Rewrite buy() and sell() to write directly to database
- Calculate portfolio value and P&L metrics in tools
- Accept job_id and session_id parameters via ContextInjector
- Return errors with proper context for debugging
- Use deployment-aware database path resolution
**Context Injection (agent/context_injector.py)**
- Add job_id and session_id to constructor
- Inject job_id and session_id into buy/sell tool calls
- Support optional parameters (None in standalone mode)
**BaseAgent (agent/base_agent/base_agent.py)**
- Read JOB_ID from runtime config
- Pass job_id to ContextInjector during initialization
- Enable automatic context injection for API mode
**ModelDayExecutor (api/model_day_executor.py)**
- Add _initialize_starting_position() method
- Create initial position record before agent runs
- Load initial_cash from config
- Update context_injector.session_id after session creation
- Link positions to sessions automatically
**Architecture Changes:**
- Eliminates file-based position tracking entirely
- Single source of truth: SQLite database
- Positions automatically linked to trading sessions
- Concurrent execution safe (no file system conflicts)
- Deployment mode aware (prod vs dev databases)
This completes the migration to database-only position storage.
File-based position.jsonl is no longer used or created.
Fixes context injection errors in concurrent simulations.
The interceptor __call__ method must be async and follow the proper
signature: async __call__(request, handler) -> result
Previous implementation was synchronous and had wrong signature, causing
'object function can't be used in await expression' error.
Resolves issue where MCP trade tools couldn't access SIGNATURE and TODAY_DATE
during concurrent API simulations, causing "SIGNATURE environment variable is
not set" errors.
Problem:
- MCP services run as separate HTTP processes
- Multiple simulations execute concurrently via ThreadPoolExecutor
- Environment variables from executor process not accessible to MCP services
Solution:
- Add ContextInjector that implements ToolCallInterceptor
- Automatically injects signature and today_date into buy/sell tool calls
- Trade tools accept optional parameters, falling back to config/env
- BaseAgent creates interceptor and updates today_date per session
Changes:
- agent/context_injector.py: New interceptor for context injection
- agent/base_agent/base_agent.py: Create and use ContextInjector
- agent_tools/tool_trade.py: Add optional signature/today_date parameters
Benefits:
- Supports concurrent multi-model simulations
- Maintains backward compatibility with CLI mode
- AI model unaware of injected parameters
- Remove _log_message() and _setup_logging() methods
- Remove all calls to logging methods in run_trading_session()
- Update log_path parameter docstring for clarity
- Update integration test to verify conversation history instead of JSONL files
- Reasoning logs now stored exclusively in database via model_day_executor
- Conversation history tracking preserved in memory
Related: Task 6 of reasoning logs API feature
- Add conversation_history instance variable to BaseAgent.__init__
- Create _capture_message() method to capture messages with timestamps
- Create get_conversation_history() method to retrieve conversation
- Create clear_conversation_history() method to reset history
- Modify run_trading_session() to capture user prompts and AI responses
- Add comprehensive unit tests for conversation tracking
- Fix datetime deprecation warning by using timezone-aware datetime
All tests pass successfully.