feat: persist autonomous alerts and surface them in session briefing (closes #161)#171
Open
luceinaltis wants to merge 2 commits into
Open
feat: persist autonomous alerts and surface them in session briefing (closes #161)#171luceinaltis wants to merge 2 commits into
luceinaltis wants to merge 2 commits into
Conversation
Closes #161. `AutonomousMonitor` was wired into the `Server` tick loop and routing to notification providers in earlier work, but overnight findings evaporated when the process exited and the `autonomous.check_interval_seconds` knob hinted at in the docs was hardcoded. This commit closes the loop: - `AutonomousAlertStore` — file-backed persistence for triggered `AutonomousAlert` records (`~/.qracer/autonomous_alerts.json`). Bounded at 500 entries, tolerant of missing/corrupt files, and reloads cross-process on mtime change (matching `AlertStore`). - `Server` now saves every autonomous alert to the store before dispatching to `NotificationRegistry`. Persistence failures are swallowed so they can't block alert delivery. - `generate_briefing()` learned an `autonomous_alert_store` argument and a new "Overnight Autonomous Findings" section that filters alerts by the previous session's mtime, caps output at 10 entries, and shows the total count in the header. - `qracer repl` and `qracer serve` both instantiate the store and thread it into their respective loops. - `app.autonomous_check_interval_seconds` (default 60s) added to `AppConfig` + `config.toml` schema; `qracer serve` now uses it instead of the CLI `--check-interval` for autonomous scans. - Docs updated to reflect the implemented status. Scope items from #161 not addressed here (roadmap): volume-spike trigger, `significance_threshold` beyond the existing price-move percentage, and integration tests with mocked price feeds beyond the unit-level fakes already in `tests/test_autonomous.py`.
`ruff check` passes individually but `ruff format --check .` flagged four files for line-collapse/wrap differences. Auto-formatted. No logic changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #161.
Earlier work wired
AutonomousMonitorinto theServertick loop and routed triggered alerts to the notification registry, but overnight findings evaporated when the process exited, the session briefing had no way to surface them, andautonomous.check_interval_seconds(called out indocs/autonomous-mode.md) was hardcoded. This PR closes that loop.What changed
qracer/autonomous.py— newAutonomousAlertStore: file-backed persistence forAutonomousAlertrecords (~/.qracer/autonomous_alerts.json). Bounded at 500 entries, tolerant of missing / malformed files, and reloads cross-process on mtime change — same pattern asAlertStore. Exposessave,get_since(dt),clear,alerts, and__len__.qracer/server.py— accepts an optionalautonomous_alert_storekwarg; every alert produced byAutonomousMonitor.check()is saved before theNotificationRegistrydispatch. A try/except around the save means a disk failure can't block alert delivery.qracer/conversation/quickpath.py—generate_briefing()gains anautonomous_alert_storekwarg and a new "Overnight Autonomous Findings" section. Alerts are filtered by the previous session's mtime, capped at 10 entries with a trailing... and N more, and the header shows the total pre-truncation count (matching thePending Taskspattern).qracer/cli.py— bothqracer replandqracer serveinstantiateAutonomousAlertStore(~/.qracer/autonomous_alerts.json)and thread it through.qracer servenow usesapp_cfg.autonomous_check_interval_secondsfor the autonomous scan cadence (formerly the CLI's shared--check-interval).qracer/config/models.py+qracer/config/schema/config.toml— newautonomous_check_interval_seconds: int = 60onAppConfig.docs/autonomous-mode.md— replaces the "구현 예정" banner with an accurate status note (what's live vs. what's still roadmap).Scope mapping (#161)
AutonomousMonitortoServer._tick()AutonomousAlertStore(simpler than a FactStore column for this payload shape)autonomous.enabledconfig keyautonomous_enabledautonomous.check_interval_secondsconfig keyautonomous.significance_thresholdprice_move_threshold_pct+ hardcoded 0.7 news-sentiment floor; a dedicated single-knob key is a follow-upItems marked⚠️ are intentionally left to follow-ups so this PR stays focused on persistence + briefing — the two user-visible gaps.
Test plan
uv run pytest tests/test_autonomous.py tests/test_server.py tests/conversation/test_quickpath.py— 109 passed (9 newTestAutonomousAlertStorecases + 3 newTestServerAutonomousPersistencecases + 3 newTestGenerateBriefingautonomous cases).uv run pytestfull suite — 793 passed, 14 skipped.uv run ruff checkon changed files — all checks passed.uv run pyrighton changed files — 0 errors.New tests cover
AutonomousAlertStore: save + cross-process roundtrip;get_sincefilters by timestamp and orders newest-first; malformedcreated_atskipped; the 500-alert cap evicts oldest;clearpersisted; missing file, malformed JSON, and partially-malformed entries all tolerated.Server: alert is saved viastore.save(alert)during_tick; no store → still notifies (backward compatible); store raisingRuntimeErrordoes not prevent the notification from firing.generate_briefing: autonomous section appears with the correct total count, filters stale alerts (>1 session old), truncates at 10 with... and N moreand shows the pre-truncation total, omits the section entirely when the store is empty.Manual verification