- Migration 031: change ai_agents/agent_variants max_tokens default
from 32768 to 4096 (32768 exceeds vLLM context window, causing
HTTP 400 on every extraction)
- API: re-enqueue approved orders to broker queue — previously
approved orders sat in DB with nothing to execute them
- values-beta: enable TRADING_ENABLED, update Alpaca paper keys
- LLMClient Protocol for provider-agnostic inference
- VLLMClient for OpenAI-compatible /v1/chat/completions API
- LLM client factory with provider routing (ollama/vllm)
- VLLMConfig with VLLM_* environment variable loading
- Updated extractor worker with health check and provider switching
- Updated event classifier to use LLMClient protocol
- Helm values for vLLM configuration
- 18 unit tests + 6 property-based tests
- Full backward compatibility preserved
Two bugs: (1) trading engine omitted estimated_value from sell order
jobs, causing risk engine to compute 0 reduction; (2) risk engine
applied position size limits to sells, trapping users in positions
they couldn't exit. Sells now always pass position value/pct checks.
- pipelineEnabled: true in beta so all pods run (Kargo happy)
- PIPELINE_DEFAULT_OFF=true in beta config — scheduler initializes
the Redis toggle to OFF on first boot
- Shared Ollama (10.1.1.12:2701) between beta and paper
- Flip pipeline ON from the UI when testing, OFF when done
- Optimistic UI update for the toggle button
- Added pipelineEnabled flag to Helm values (default: true)
- Worker services (scheduler, ingestion, parser, extractor, aggregation,
recommendation, broker-adapter, lake-publisher) scale to 0 when disabled
- API services always run regardless of toggle
- Redis-based runtime toggle: POST /api/ops/pipeline/toggle
- Scheduler checks the flag before each cycle
- Frontend: green/red Pipeline ON/OFF button on the pipeline page
- Beta defaults to pipelineEnabled: false
- Base values.yaml: blanked external URLs (Ollama, Polygon, Alpaca)
so stages only connect to what they explicitly configure
The 30-minute threshold was shorter than the queue drain time, causing
the recovery sweep to re-enqueue docs that were already queued but not
yet processed. Bumped to 4 hours with matching marker TTL.
Recovery sweeps and the retry endpoint now check a per-document Redis
key (SET NX, 1h TTL) before pushing to the queue. If the marker exists,
the doc is already enqueued and gets skipped. This prevents the
scheduler from re-enqueuing the same parsed docs every 5 minutes.
The pipeline health, SSE stream, and retry endpoints were hardcoding
'stonks:queue:{name}' but services use DEPLOY_STAGE prefix
('stonks:paper:queue:{name}'). Now uses queue_key() from redis_keys.py.
- POST /api/ops/pipeline/retry-failed endpoint resets extraction_failed
docs to parsed, deletes failed intelligence rows, and re-enqueues
them (batch of 200)
- Scheduler now auto-retries extraction_failed docs every ~10 minutes
(100 per cycle, 60-min cooldown per doc)
- Pipeline page shows 'Retry Failed (N)' button when extraction_failed
count > 0, with pending/success/error states
The inline catalyst_type query in GET /api/patterns/{ticker} referenced
dir.document_id which does not exist on document_impact_records. The
table links to documents via intelligence_id -> document_intelligence ->
document_id. Added the missing JOIN to match the pattern used in
_SELF_PATTERN_QUERY.
1. patterns endpoint: fix query referencing non-existent column
di.catalyst_type → dir.catalyst_type (column is on document_impact_records)
2. lockouts seed: use relative timestamps (now + 7d) so active lockout
is always in the future regardless of when tests run
3. create_agent: make slug optional with auto-generation from name
4. create_source: json.dumps(config) + ::jsonb cast for asyncpg JSONB compat
5. approval_expiry: return count as int (len(expired)) not the list itself
6. metrics_consistency: fix test assertion to match API contract
(total >= active + reserve, not total == active + reserve + unrealized)
_parse_classification_response receives raw model output (with thinking
tags, markdown fences, etc.) but was calling json.loads directly.
Now uses _strip_markdown_fences + _repair_json from the client module
before parsing, matching what _call_ollama does for extractions.
_call_ollama validates against the document extraction schema, which
doesn't match event classification output. The event classifier was
checking 'if attempt.error is None' before trying its own parsing,
so it never got to parse the valid event JSON — 956 consecutive
failures.
Now tries _parse_classification_response whenever raw_output exists,
regardless of the extraction validation error.
Backend: assemble_trend_with_evidence now deduplicates document IDs
via dict.fromkeys() (the rollup code already did this, but the base
assembly didn't — same doc could appear multiple times from different
intelligence extractions).
Frontend: Trends.tsx deduplicates via Set before rendering as a safety
net for existing data already stored with duplicates.
- recommendation worker: filter out non-UUID document IDs (synthetic
pattern:* IDs from competitive signals) before inserting into
recommendation_evidence table — the uuid cast was failing and
silently dropping all evidence rows
- wrap executemany in try/except so partial failures don't lose all evidence
- SqlExplorer: wrap Lucide icons in <span title=...> instead of passing
title prop directly (not supported by lucide-react, broke CI build)
- Add GET/PUT /api/admin/trading/approval-config endpoints
- Add POST/DELETE /api/admin/trading/lockouts endpoints
- Add useApprovalConfig, useUpdateApprovalConfig, useCreateLockout, useDeleteLockout hooks
- Add Paper Order Approval toggle card with confirmation dialog
- Add lockout creation form and delete button to Active Lockouts card
- Add MSW handlers for all new endpoints
- Add property-based tests for bug condition exploration and preservation
API was returning a flat array but frontend expects CompanyMacroImpacts
wrapper with exposure_profile and impacts fields. Also queries the
exposure_profiles table for the company's active profile.
- Trading page: added conservative/moderate/aggressive selector that
updates the trading engine config via PUT /api/trading/config
- Recommendations page: added risk tier dropdown that defaults to the
engine's current tier and filters recs by the tier's min_confidence
- Backend: added min_confidence query param to GET /api/recommendations
- Risk tier thresholds: conservative ≥0.75, moderate ≥0.55, aggressive ≥0.40
- Removed PUT /api/trading/capital (set capital) — only touched in-memory state
- Removed POST /api/trading/capital/adjust (add/withdraw) — same problem
- Reset endpoint now: liquidates Alpaca positions, cancels orders, clears DB,
then queries Alpaca for real portfolio_value to set engine capital
- Frontend: replaced CapitalCard with simple ResetCard (one button)
- Removed useSetTradingCapital and useAdjustCapital hooks
- Added cancel_all_orders() and close_all_positions() to AlpacaBrokerAdapter
- Reset endpoint creates a temporary adapter to call Alpaca DELETE /v2/orders
and DELETE /v2/positions before clearing DB and engine state
- Also clears positions table and processed_recommendation_ids on reset
- Broker reset is best-effort — DB/engine reset proceeds even if Alpaca fails
Agreement of 1-2 signals was inflating confidence to paper-eligible
levels (0.575) even with low credibility sources. Added log2-based
dampener that scales agreement contribution by unique source count,
saturating at n=7. Single signals now cap at 0.39 confidence,
2 signals at 0.49 — both correctly below paper threshold (0.50).
- Recommendation worker now resolves thesis-rewriter config from DB
and passes ollama_config to generate_recommendation. Thesis rewriting
is now active when the thesis-rewriter agent exists in ai_agents.
Refreshes config every 50 jobs.
- Event classifier now resolves its own config separately from the
document extractor via 'event-classifier' slug. Uses a separate
OllamaClient when the model differs from the extractor. Refreshes
alongside the extractor every 100 jobs.
- Document extractor was already wired (existing code).
- Added 8 unit tests for AgentConfigResolver covering: DB resolution,
variant override, not-found, DB errors, TTL caching, cache refresh,
and invalidation.
- Migration 026 and OllamaConfig now default to qwen3.5:9b instead of
llama3.1:8b. Existing deployments keep their current model (qwen3.5:9b-fast)
since the migration uses WHERE NOT EXISTS on slug.
- Event classifier system prompt expanded with macro-vs-company filtering:
explicitly instructs the model to NOT classify single-company news
(lawsuits, earnings, management changes, debt crises) as macro events.
Sets severity=low and confidence<0.3 for company-specific articles.
Reserves 'critical' severity for multi-country/global market events.
Prevents over-tagging event_types by requiring direct description.
- Updated test_system_prompt_is_concise threshold to accommodate the
expanded prompt (300 → 1000 chars).
Three distinct capital operations on the Trading Controls page:
- Set Capital: overwrites pool balances to a new amount (existing)
- Add/Withdraw: adjusts active pool by a delta without touching
positions, orders, or history. Validates sufficient balance for
withdrawals. Logged to reserve_pool_ledger as manual_adjustment.
- Reset Everything: nuclear option — deletes all positions, orders,
trading decisions, stop levels, snapshots, backtests, notifications,
and circuit breaker events, then resets capital fresh. Red button
with double-confirmation dialog.
Backend: POST /api/trading/capital/adjust and POST /api/trading/reset
Frontend: CapitalCard rebuilt with three sections and confirmation UIs
New Agents tab in the sidebar (Ops group) for viewing, editing, and
creating AI agent configurations:
Database (migration 026):
- ai_agents table: editable configs for each LLM agent (model, prompts,
temperature, tokens, retries). source='system' for built-in,
source='user' for custom. Seeds 3 system agents (Document Extractor,
Event Classifier, Thesis Rewriter) using WHERE NOT EXISTS to never
overwrite user edits across reinstalls.
- agent_performance_log table: per-invocation metrics (duration,
confidence, retries, tokens, errors) linked to agent config.
API endpoints:
- GET/POST /api/agents — list and create agents
- GET/PUT/DELETE /api/agents/{id} — view, edit, delete (system agents
can be edited but not deleted)
- GET /api/agents/{id}/performance — aggregated metrics (success rate,
avg/p95 latency, confidence, token usage)
- GET /api/agents/{id}/performance/history — hourly time series
Frontend:
- AgentsPage with sidebar list + detail panel
- Agent detail: config display, system prompt viewer, performance
dashboard with metrics cards and time-series chart
- Edit form: all config fields editable including system prompt,
model, temperature, tokens, retries
- Create form: new user-defined agents with auto-slug generation
- System agents show blue badge, user agents show green badge
- New 'intraday_bars' endpoint in PolygonMarketAdapter: fetches hourly
bars for today using range_bars URL with timespan=hour, sort=asc
- Scheduler expands intraday_bars global source into per-ticker jobs
for all active companies (every 15 minutes via polling_interval)
- Migration 025 inserts the intraday source with 900s cadence
- Frontend price matching uses closest-timestamp instead of date-string
matching, with 2h tolerance for intraday and 36h for daily windows
- Bumped market price fetch limit to 200 for intraday granularity
- New GET /api/market/prices/{ticker} endpoint serving OHLCV data from
market_snapshots, deduped by bar_timestamp
- New useMarketPrices hook in frontend
- Trend chart now shows price (purple line) on a right Y axis ($)
alongside trend metrics (%) on the left Y axis
- Custom tooltip formats price as dollars, metrics as percentages
- Price line uses connectNulls for days with missing bar data