amfs-adapter-postgres 0.2.28__tar.gz → 0.2.29__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. {amfs_adapter_postgres-0.2.28 → amfs_adapter_postgres-0.2.29}/PKG-INFO +1 -1
  2. {amfs_adapter_postgres-0.2.28 → amfs_adapter_postgres-0.2.29}/pyproject.toml +1 -1
  3. {amfs_adapter_postgres-0.2.28 → amfs_adapter_postgres-0.2.29}/src/amfs_postgres/adapter.py +53 -22
  4. {amfs_adapter_postgres-0.2.28 → amfs_adapter_postgres-0.2.29}/.gitignore +0 -0
  5. {amfs_adapter_postgres-0.2.28 → amfs_adapter_postgres-0.2.29}/src/amfs_postgres/__init__.py +0 -0
  6. {amfs_adapter_postgres-0.2.28 → amfs_adapter_postgres-0.2.29}/src/amfs_postgres/_fts.py +0 -0
  7. {amfs_adapter_postgres-0.2.28 → amfs_adapter_postgres-0.2.29}/src/amfs_postgres/async_adapter.py +0 -0
  8. {amfs_adapter_postgres-0.2.28 → amfs_adapter_postgres-0.2.29}/src/amfs_postgres/knn.py +0 -0
  9. {amfs_adapter_postgres-0.2.28 → amfs_adapter_postgres-0.2.29}/src/amfs_postgres/migrations/001_initial.sql +0 -0
  10. {amfs_adapter_postgres-0.2.28 → amfs_adapter_postgres-0.2.29}/src/amfs_postgres/migrations/002_artifact_refs.sql +0 -0
  11. {amfs_adapter_postgres-0.2.28 → amfs_adapter_postgres-0.2.29}/src/amfs_postgres/migrations/002_search_indexes.sql +0 -0
  12. {amfs_adapter_postgres-0.2.28 → amfs_adapter_postgres-0.2.29}/src/amfs_postgres/migrations/003_knowledge_graph.sql +0 -0
  13. {amfs_adapter_postgres-0.2.28 → amfs_adapter_postgres-0.2.29}/src/amfs_postgres/migrations/004_agent_groups.sql +0 -0
  14. {amfs_adapter_postgres-0.2.28 → amfs_adapter_postgres-0.2.29}/src/amfs_postgres/migrations/005_configurable_embedding_dim.sql +0 -0
  15. {amfs_adapter_postgres-0.2.28 → amfs_adapter_postgres-0.2.29}/src/amfs_postgres/migrations/006_is_artifact.sql +0 -0
  16. {amfs_adapter_postgres-0.2.28 → amfs_adapter_postgres-0.2.29}/src/amfs_postgres/migrations/006_outcome_confidence_semantics.sql +0 -0
  17. {amfs_adapter_postgres-0.2.28 → amfs_adapter_postgres-0.2.29}/src/amfs_postgres/migrations/007_outcome_propagation_row_copy.sql +0 -0
  18. {amfs_adapter_postgres-0.2.28 → amfs_adapter_postgres-0.2.29}/src/amfs_postgres/migrations/008_outcome_evidence.sql +0 -0
  19. {amfs_adapter_postgres-0.2.28 → amfs_adapter_postgres-0.2.29}/src/amfs_postgres/migrations/009_action_priors.sql +0 -0
  20. {amfs_adapter_postgres-0.2.28 → amfs_adapter_postgres-0.2.29}/src/amfs_postgres/migrations/010_local_evidence.sql +0 -0
  21. {amfs_adapter_postgres-0.2.28 → amfs_adapter_postgres-0.2.29}/src/amfs_postgres/migrations/011_outcome_task_text.sql +0 -0
  22. {amfs_adapter_postgres-0.2.28 → amfs_adapter_postgres-0.2.29}/src/amfs_postgres/retention.py +0 -0
  23. {amfs_adapter_postgres-0.2.28 → amfs_adapter_postgres-0.2.29}/src/amfs_postgres/schema.sql +0 -0
  24. {amfs_adapter_postgres-0.2.28 → amfs_adapter_postgres-0.2.29}/src/amfs_postgres/tenant_context.py +0 -0
  25. {amfs_adapter_postgres-0.2.28 → amfs_adapter_postgres-0.2.29}/src/amfs_postgres/tenant_gucs.py +0 -0
  26. {amfs_adapter_postgres-0.2.28 → amfs_adapter_postgres-0.2.29}/src/amfs_postgres/trace_partitions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: amfs-adapter-postgres
3
- Version: 0.2.28
3
+ Version: 0.2.29
4
4
  Summary: AMFS Postgres adapter with back-propagation triggers
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: >=3.11
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "amfs-adapter-postgres"
3
- version = "0.2.28"
3
+ version = "0.2.29"
4
4
  description = "AMFS Postgres adapter with back-propagation triggers"
5
5
  requires-python = ">=3.11"
6
6
  license = "Apache-2.0"
@@ -465,12 +465,26 @@ from amfs_core.aggregates import ( # noqa: E402
465
465
  RECALL_TOKENS_FLOOR as _RECALL_TOKENS_FLOOR,
466
466
  )
467
467
 
468
+ # Guarded by recall_count, because an entry never recalled contributes zero
469
+ # whatever its size, and ``value::text`` is the expensive part: it serialises
470
+ # the whole JSONB document for every row the aggregate sees. Multiplying that
471
+ # by zero afterwards still paid for it — on 400k entries it was half the
472
+ # ``/stats`` statement (4.8 s → 2.3 s with the guard, same result) and half
473
+ # of ``/entities``. CASE evaluates only the branch taken, so the unrecalled
474
+ # majority of rows skip the serialisation.
468
475
  _RECALLED_TOKENS_SQL = (
469
- "COALESCE(SUM(recall_count * LEAST(GREATEST("
476
+ "COALESCE(SUM(CASE WHEN recall_count > 0 THEN recall_count * LEAST(GREATEST("
470
477
  f"length(value::text) / {_CHARS_PER_TOKEN}, {_RECALL_TOKENS_FLOOR}), "
471
- f"{_RECALL_TOKENS_CEIL})), 0)"
478
+ f"{_RECALL_TOKENS_CEIL}) ELSE 0 END), 0)"
472
479
  )
473
480
 
481
+ #: ``work_mem`` for the grouped aggregate statements below. Grouping 400k
482
+ #: entries by entity_path spilled to disk at the 4 MB default (``Sort Method:
483
+ #: external merge``) and took 1.9 s; at 64 MB the same statement hash-
484
+ #: aggregates in memory in 0.6 s. Set ``LOCAL`` so it ends with the
485
+ #: transaction and cannot leak to whatever borrows the pooled connection next.
486
+ _AGGREGATE_WORK_MEM = "64MB"
487
+
474
488
 
475
489
  class _SingleConnectionPool:
476
490
  """Minimal pool shim wrapping a single connection for environments
@@ -3360,6 +3374,10 @@ class PostgresAdapter(AdapterABC):
3360
3374
  """
3361
3375
  with self._pool.connection() as conn:
3362
3376
  with conn.cursor() as cur:
3377
+ # The checkout is one transaction (see _TenantRLSConnection),
3378
+ # so LOCAL ends with it and the pooled connection goes back
3379
+ # with the default.
3380
+ cur.execute(f"SET LOCAL work_mem = '{_AGGREGATE_WORK_MEM}'")
3363
3381
  cur.execute(sql, params)
3364
3382
  rows = cur.fetchall()
3365
3383
 
@@ -3537,12 +3555,19 @@ class PostgresAdapter(AdapterABC):
3537
3555
 
3538
3556
  with self._pool.connection() as conn:
3539
3557
  with conn.cursor() as cur:
3558
+ # The checkout is one transaction (see _TenantRLSConnection),
3559
+ # so LOCAL ends with it and the pooled connection goes back
3560
+ # with the default.
3561
+ cur.execute(f"SET LOCAL work_mem = '{_AGGREGATE_WORK_MEM}'")
3562
+ # No COUNT(DISTINCT ...) here: the two distinct counts are the
3563
+ # row counts of the agent and entity breakdowns below, over the
3564
+ # same WHERE. As aggregates they forced two sorts of every row
3565
+ # and kept the statement off the parallel plan — 2.3 s against
3566
+ # 0.24 s for the same answer on 400k entries.
3540
3567
  cur.execute(
3541
3568
  f"""
3542
3569
  SELECT
3543
3570
  COUNT(*) AS total_entries,
3544
- COUNT(DISTINCT entity_path) AS total_entities,
3545
- COUNT(DISTINCT agent_id) AS total_agents,
3546
3571
  AVG(confidence) AS confidence_avg,
3547
3572
  MIN(confidence) AS confidence_min,
3548
3573
  MAX(confidence) AS confidence_max,
@@ -3618,22 +3643,28 @@ class PostgresAdapter(AdapterABC):
3618
3643
  recalls_this_week = 0
3619
3644
  recalls_last_week = 0
3620
3645
  try:
3621
- cur.execute(
3622
- f"""
3623
- SELECT
3624
- COUNT(*) FILTER (
3625
- WHERE created_at >= NOW() - INTERVAL '7 days'
3626
- ) AS recalls_this_week,
3627
- COUNT(*) FILTER (
3628
- WHERE created_at >= NOW() - INTERVAL '14 days'
3629
- AND created_at < NOW() - INTERVAL '7 days'
3630
- ) AS recalls_last_week
3631
- FROM amfs_events
3632
- WHERE {event_where}
3633
- """,
3634
- event_params,
3635
- )
3636
- event_row = cur.fetchone()
3646
+ # Its own savepoint: a failed statement aborts the
3647
+ # transaction it runs in, and this method is inside the
3648
+ # checkout's. Rolling back to the savepoint leaves that
3649
+ # transaction healthy, so the checkout still commits and
3650
+ # the connection goes back to the pool usable.
3651
+ with conn.transaction():
3652
+ cur.execute(
3653
+ f"""
3654
+ SELECT
3655
+ COUNT(*) FILTER (
3656
+ WHERE created_at >= NOW() - INTERVAL '7 days'
3657
+ ) AS recalls_this_week,
3658
+ COUNT(*) FILTER (
3659
+ WHERE created_at >= NOW() - INTERVAL '14 days'
3660
+ AND created_at < NOW() - INTERVAL '7 days'
3661
+ ) AS recalls_last_week
3662
+ FROM amfs_events
3663
+ WHERE {event_where}
3664
+ """,
3665
+ event_params,
3666
+ )
3667
+ event_row = cur.fetchone()
3637
3668
  if event_row:
3638
3669
  recalls_this_week = int(event_row["recalls_this_week"] or 0)
3639
3670
  recalls_last_week = int(event_row["recalls_last_week"] or 0)
@@ -3646,8 +3677,8 @@ class PostgresAdapter(AdapterABC):
3646
3677
  "recalls_this_week": recalls_this_week,
3647
3678
  "recalls_last_week": recalls_last_week,
3648
3679
  "total_entries": row["total_entries"] if row else 0,
3649
- "total_entities": row["total_entities"] if row else 0,
3650
- "total_agents": row["total_agents"] if row else 0,
3680
+ "total_entities": len(entity_rows),
3681
+ "total_agents": len(agent_rows),
3651
3682
  "agents": {r["agent_id"]: r["cnt"] for r in agent_rows},
3652
3683
  "entities": {r["entity_path"]: r["cnt"] for r in entity_rows},
3653
3684
  "confidence_avg": float(row["confidence_avg"] or 0) if row else 0.0,