Skip to content

Commit b901b5d

Browse files
feat(serving): API-usage + session analytics behind ControlPlaneStore (ADR 0010 slice 4) (#134)
api_usage/api_sessions never shared query_engine._conn (AuthManager.db_path is always its own file), so EmbeddedControlPlaneStore gains a second, orthogonal usage_db_path_provider alongside conn_provider. Scope widened past usage_table.py/analytics.py: key_rotation.py's old-key-usage queries and admin_ui.py's QPS tile also queried these tables directly and are covered here too, so slice 5's PostgreSQL swap doesn't leave two call sites hard-wired to a local DuckDB file. All pre-port public/test-facing signatures preserved. Co-authored-by: JuliaEdom <uedomskikh@gmail.com>
1 parent 4a97cf5 commit b901b5d

14 files changed

Lines changed: 1092 additions & 797 deletions

‎CHANGELOG.md‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,36 @@ All notable changes to AgentFlow are documented in this file.
44

55
## [Unreleased]
66

7+
### Added — API-usage accounting and session analytics behind the ControlPlaneStore port (ADR 0010 slice 4, 2026-07-02)
8+
9+
- **`api_usage`** (per-tenant/per-key request counters) and **`api_sessions`**
10+
(per-request latency/entity/query telemetry) moved behind
11+
`ControlPlaneStore`. Unlike every prior slice, this state was never on
12+
`query_engine._conn` — `AuthManager.db_path` always resolves to its own
13+
DuckDB file, independent of `DUCKDB_PATH` — so `EmbeddedControlPlaneStore`
14+
gains a second, orthogonal `usage_db_path_provider` alongside its existing
15+
`conn_provider`/`alert_rules_path_provider`, and `AuthManager` builds a
16+
private embedded store bound to its own path (mirrors `OutboxProcessor`'s
17+
additive-`store` pattern) rather than sharing the app-wide store.
18+
- **Scope widened past `usage_table.py` + `analytics.py`**: `KeyRotator`
19+
(`key_rotation.py`) queried `api_usage` directly for old-key-usage stats,
20+
and the admin dashboard (`admin_ui.py`) queried `api_sessions` directly for
21+
its QPS tile — both bypassed every prior slice's port and are covered here
22+
too, so a PostgreSQL swap (slice 5) doesn't leave two call sites hard-wired
23+
to a local DuckDB file.
24+
- `record_usage`'s Windows file-lock fallback (temp-path rename on
25+
`duckdb.IOException`) and `record_api_session`'s best-effort
26+
log-and-return-on-exhaustion (vs. `record_api_usage`'s raise, which
27+
`record_usage` depends on to skip its post-insert audit publish) both moved
28+
into the store verbatim. `usage_table.py` / `analytics.py` / `key_rotation.py`
29+
/ `admin_ui.py` keep their pre-port public/test-facing signatures — only
30+
the SQL and connection handling moved.
31+
- New `EmbeddedControlPlaneStore` methods: `ensure_usage_schema`,
32+
`record_api_usage`, `get_usage_by_tenant`, `get_usage_by_key`,
33+
`get_old_key_usage_by_key_id`, `record_api_session`, `get_usage_analytics`,
34+
`get_top_queries`, `get_top_entities`, `get_latency_analytics`,
35+
`get_anomalies`, `get_queries_per_second_last_minute`.
36+
737
### Added — Replay outbox + dead-letter behind the ControlPlaneStore port (ADR 0010 slice 3, 2026-07-02)
838

939
- **Invariant 8 preserved verbatim**: `mark_outbox_sent` flips an outbox row

‎docs/decisions/0010-control-plane-externalization-postgres.md‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,11 @@ ClickHouse is consumed), and `psycopg` joins the optional dependencies.
178178
2. Alert history + alert rules/runtime-state repository behind the port
179179
(embedded impl keeps the YAML file format).
180180
3. Outbox + dead-letter behind the port (invariant 8 preserved verbatim).
181-
4. Usage/sessions behind the port (pooled writes replace the
182-
per-request file connections in `usage_table.py`).
181+
4. Usage/sessions behind the port — `usage_table.py`, `analytics.py`,
182+
`key_rotation.py`'s old-key-usage queries and `admin_ui.py`'s QPS tile all
183+
queried `api_usage`/`api_sessions` directly; all four move behind the port
184+
(pure extraction, per-request file connections unchanged — pooling is not
185+
part of this ADR).
183186
5. `PostgresControlPlaneStore` + live verification (standalone-PG probe
184187
suite: parallel claim exclusivity, lease expiry re-drive, restart
185188
re-drive, enqueue-win uniqueness, outbox↔dead-letter atomicity) +

0 commit comments

Comments
 (0)