fix(alerts): enforce session limits for llm-only ingest - #737
Conversation
|
| Filename | Overview |
|---|---|
| tokenjam/core/alerts.py | Adds session-scoped lifetime deduplication, locking, and restart hydration, but bounded hydration still permits older active-session crossings to refire. |
| tokenjam/core/ingest.py | Evaluates active-session limits after cost processing in both synchronous and asynchronous hook modes. |
| tests/synthetic/test_alert_engine_restart.py | Covers ordinary restart and concurrent-evaluation deduplication, but does not exercise hydration-page eviction. |
| tests/integration/test_full_pipeline.py | Adds end-to-end coverage for cost and duration alerts on LLM-only active sessions. |
| tests/unit/test_async_hooks.py | Verifies deferred session-progress evaluation and fatal storage-error handling. |
| tests/synthetic/test_alert_rules.py | Adds direct rule coverage for active-session cost and duration threshold evaluation. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
A[Ingest span] --> B[Persist span and session]
B --> C[Compute and persist cost]
C --> D{Async advisory hooks?}
D -->|No| E[Evaluate active-session progress]
D -->|Yes| F[Advisory worker]
F --> E
E --> G{Budget or duration exceeded?}
G -->|No| H[Continue]
G -->|Yes| I{Session/type key hydrated or fired?}
I -->|Yes| H
I -->|No| J[Persist and dispatch alert]
Prompt To Fix All With AI
### Issue 1
tokenjam/core/alerts.py:75
**Hydration cap permits refiring**
When a still-active session's prior cost or duration alert is older than 10,000 newer alerts of that type, restart hydration omits its lifetime deduplication key. The next span then treats the persistent threshold crossing as new, inserting and dispatching the same alert again.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (6): Last reviewed commit: "Merge branch 'main' into fix/554-session..." | Re-trigger Greptile
af39782 to
245df69
Compare
|
Addressed the P1 review in commit 245df69: session-scoped budget and duration alerts now use session-aware cooldown keys and a per-session fired latch hydrated from persisted rows. Repeated spans in one session produce no duplicate rows, while separate sessions for the same agent alert independently; regression coverage includes both cases. |
|
Addressed the three P1 review findings in commit
Validation:
|
|
The P1 finding is addressed in the current head
The focused restart, rule, and pipeline regression tests pass as documented in the earlier PR update. |
|
@mikemikimike thanks for this. Your approach is the one I want to build on, so I'd like to get it landed. Now that the workflow is approved, CI is red on two things: 1. mypy, in the 2. That second one is worth more than a quick fix. 3. Critical Rule 45(b): the new Smaller things, none blocking:
I verified your tests are load-bearing: reverting One thing you should know: @Mr-Neutr0n independently fixed the same issue in #738. I'm going with yours because the latch placement inside |
Keep advisory alert work off the ingest thread when async hooks are enabled, while reusing the already-updated session total on the synchronous path. Guard the threshold checks before allocating alert objects and route progress-read failures through fatal database handling. Co-Authored-By: anilmurty <noreply@github.com>
| session_alerts = self.db.get_alerts(AlertFilters( | ||
| type=alert_type, | ||
| limit=_SESSION_LIMIT_HYDRATION_LIMIT, | ||
| )) |
There was a problem hiding this comment.
Hydration cap permits refiring
When a still-active session's prior crossing is older than 10,000 newer alerts of the same type, startup hydration omits its lifetime dedup key. The next span then inserts and dispatches the same session-limit alert again.
Knowledge Base Used:
Prompt To Fix With AI
This is a comment left during a code review.
Path: tokenjam/core/alerts.py
Line: 303-306
Comment:
**Hydration cap permits refiring**
When a still-active session's prior crossing is older than 10,000 newer alerts of the same type, startup hydration omits its lifetime dedup key. The next span then inserts and dispatches the same session-limit alert again.
**Knowledge Base Used:**
- [Optimization and governance](https://app.greptile.com/metabldr/-/custom-context/knowledge-base/metabuilder-labs/tokenjam/-/docs/optimization-and-governance.md)
- [Policies, alerts, and drift](https://app.greptile.com/metabldr/-/custom-context/knowledge-base/metabuilder-labs/tokenjam/-/docs/policies-alerts-and-drift.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Keep the session identifier narrowed across the database lookup so the lint job accepts the progress hook. Serialize the async hook test around the worker to verify advisory hooks remain deferred without depending on thread scheduling. Co-Authored-By: anilmurty <noreply@github.com>
| # their dedup state must be hydrated beyond the cooldown window. Use the same | ||
| # bounded page as the ordinary hydration queries; the targeted per-type scans | ||
| # avoid making every AlertEngine construction pay for an oversized table scan. | ||
| _SESSION_LIMIT_HYDRATION_LIMIT = _HYDRATION_LIMIT |
There was a problem hiding this comment.
Hydration cap permits refiring
When a still-active session's prior cost or duration alert is older than 10,000 newer alerts of that type, restart hydration omits its lifetime deduplication key. The next span then treats the persistent threshold crossing as new, inserting and dispatching the same alert again.
Knowledge Base Used:
Prompt To Fix With AI
This is a comment left during a code review.
Path: tokenjam/core/alerts.py
Line: 75
Comment:
**Hydration cap permits refiring**
When a still-active session's prior cost or duration alert is older than 10,000 newer alerts of that type, restart hydration omits its lifetime deduplication key. The next span then treats the persistent threshold crossing as new, inserting and dispatching the same alert again.
**Knowledge Base Used:**
- [Optimization and governance](https://app.greptile.com/metabldr/-/custom-context/knowledge-base/metabuilder-labs/tokenjam/-/docs/optimization-and-governance.md)
- [Policies, alerts, and drift](https://app.greptile.com/metabldr/-/custom-context/knowledge-base/metabuilder-labs/tokenjam/-/docs/policies-alerts-and-drift.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
|
@mikemikimike you've addressed all three. I read the diff rather than just the green checks:
On the test change — I looked at this carefully, because replacing an assertion that just started failing is exactly how a guard gets quietly removed, and we have a standing rule about it. Your diagnosis is correct and the change is sound: the old Worth knowing what the new version does and doesn't prove: The red CI you were fighting was never yours. Once the matrix comes back green this is ready to merge. Nothing further from me. |
anilmurty
left a comment
There was a problem hiding this comment.
Thanks @mikemikimike — merging. You landed all three asks: async_hooks is respected so alert evaluation stays off the ingest thread, handle_if_fatal is wired into the progress read, and you picked up the threshold pre-check from @Mr-Neutr0n's #738.
Triaging Greptile's remaining open issue ("Hydration cap permits refiring", 4/5) rather than merging past it: it is real but bounded, and acceptable. get_alerts is ORDER BY fired_at DESC LIMIT 10000, so the cap drops the oldest rows — a still-active session whose alert fired more than 10,000 same-type alerts ago loses its dedup key on restart and refires once. That is the same bounded tradeoff _HYDRATION_LIMIT already carries on main for the other alert types, and it is documented in the constant's comment: a missed stale row degrades to treating an old alert as new, never to suppressing wrongly. One duplicate row in a corner that needs 10k+ same-type alerts is not worth another round trip. Filing it as a follow-up if it ever shows up in practice.
Also worth recording: the red CI on this PR was never yours — main was broken by an expired claude-sonnet-5 introductory rate, fixed in #741.
Summary
Streams that emit only gen_ai.llm.call spans remain active and never reach the session-end alert hook. This change evaluates session cost and duration limits after post-ingest cost processing, so computed costs are included while daily budgets remain end-triggered.
Changes
Validation
uff check tokenjam/ — source and touched tests pass.