amfs-adapter-postgres 0.2.43__tar.gz → 0.2.44__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.
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/PKG-INFO +2 -2
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/pyproject.toml +2 -2
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/adapter.py +35 -8
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/async_adapter.py +16 -3
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/.gitignore +0 -0
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/__init__.py +0 -0
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/_fts.py +0 -0
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/evidence_sql.py +0 -0
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/knn.py +0 -0
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/001_initial.sql +0 -0
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/002_artifact_refs.sql +0 -0
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/002_search_indexes.sql +0 -0
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/003_knowledge_graph.sql +0 -0
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/004_agent_groups.sql +0 -0
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/005_configurable_embedding_dim.sql +0 -0
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/006_is_artifact.sql +0 -0
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/006_outcome_confidence_semantics.sql +0 -0
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/007_outcome_propagation_row_copy.sql +0 -0
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/008_outcome_evidence.sql +0 -0
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/009_action_priors.sql +0 -0
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/010_local_evidence.sql +0 -0
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/011_outcome_task_text.sql +0 -0
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/012_evidence_from_model.sql +0 -0
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/013_outcome_verified_by_backfill.sql +0 -0
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/014_outcome_covariates.sql +0 -0
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/retention.py +0 -0
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/schema.sql +0 -0
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/tenant_context.py +0 -0
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/tenant_gucs.py +0 -0
- {amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/trace_partitions.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: amfs-adapter-postgres
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.44
|
|
4
4
|
Summary: AMFS Postgres adapter with back-propagation triggers
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Requires-Python: >=3.11
|
|
7
|
-
Requires-Dist: amfs-core>=0.3.
|
|
7
|
+
Requires-Dist: amfs-core>=0.3.56
|
|
8
8
|
Requires-Dist: psycopg-pool>=3.1
|
|
9
9
|
Requires-Dist: psycopg[binary]<4,>=3.1
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "amfs-adapter-postgres"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.44"
|
|
4
4
|
description = "AMFS Postgres adapter with back-propagation triggers"
|
|
5
5
|
requires-python = ">=3.11"
|
|
6
6
|
license = "Apache-2.0"
|
|
7
7
|
dependencies = [
|
|
8
8
|
# 0.3.14 adds the capture fields this adapter now persists and selects back,
|
|
9
9
|
# tool_calls among them.
|
|
10
|
-
"amfs-core>=0.3.
|
|
10
|
+
"amfs-core>=0.3.56",
|
|
11
11
|
"psycopg[binary]>=3.1,<4",
|
|
12
12
|
"psycopg-pool>=3.1",
|
|
13
13
|
]
|
|
@@ -222,6 +222,14 @@ def priority_score_sql(now: datetime, config: Any | None = None) -> tuple[str, l
|
|
|
222
222
|
return expr, params
|
|
223
223
|
|
|
224
224
|
|
|
225
|
+
#: A row whose TTL has not passed: the predicate every read path that
|
|
226
|
+
#: surfaces current entries carries (``list``, ``search``, ``semantic_search``,
|
|
227
|
+
#: the agent listing). ``NOW()`` rather than a parameter because the fragment
|
|
228
|
+
#: sits in WHERE lists whose parameters are positional and spliced around by
|
|
229
|
+
#: their callers; with nothing to bind it cannot shift any of them.
|
|
230
|
+
_NOT_EXPIRED = "(ttl_at IS NULL OR ttl_at > NOW())"
|
|
231
|
+
|
|
232
|
+
|
|
225
233
|
def list_conditions(
|
|
226
234
|
namespace: str,
|
|
227
235
|
entity_path: str | None,
|
|
@@ -231,6 +239,7 @@ def list_conditions(
|
|
|
231
239
|
scope: SqlScope | None,
|
|
232
240
|
agent_id: str | None,
|
|
233
241
|
branch_scope: tuple[str, list[Any]] | None = None,
|
|
242
|
+
include_expired: bool = False,
|
|
234
243
|
) -> tuple[list[str], list[Any]]:
|
|
235
244
|
"""The WHERE behind ``list()`` and ``count_entries()`` on both adapters.
|
|
236
245
|
|
|
@@ -238,6 +247,13 @@ def list_conditions(
|
|
|
238
247
|
about which rows exist, and so the shared-namespace guard is applied to
|
|
239
248
|
the unscoped read in one place (``tests/test_shared_path_scoping.py``).
|
|
240
249
|
|
|
250
|
+
The rows are the live ones: current and not past their ``ttl_at``, which
|
|
251
|
+
is the rule ``read`` and ``search`` apply, so a TTL takes effect on every
|
|
252
|
+
listing at once instead of when the sweep next archives the row. Two
|
|
253
|
+
callers see expired rows all the same. *include_superseded* is the
|
|
254
|
+
history, which hides nothing; *include_expired* is for the sweep, whose
|
|
255
|
+
whole purpose is to find them (``list_expired``).
|
|
256
|
+
|
|
241
257
|
*branch_scope* replaces the plain ``branch = %s`` predicate with the
|
|
242
258
|
overlay fragment from :func:`branch_scope_sql` (a branch's own live rows
|
|
243
259
|
plus the parent's for every key it has not touched) — the adapters pass
|
|
@@ -258,6 +274,8 @@ def list_conditions(
|
|
|
258
274
|
|
|
259
275
|
if not include_superseded:
|
|
260
276
|
conditions.append("superseded_at IS NULL")
|
|
277
|
+
if not include_expired:
|
|
278
|
+
conditions.append(_NOT_EXPIRED)
|
|
261
279
|
if agent_id is not None:
|
|
262
280
|
conditions.append("agent_id = %s")
|
|
263
281
|
params.append(agent_id)
|
|
@@ -2453,13 +2471,15 @@ class PostgresAdapter(AdapterABC):
|
|
|
2453
2471
|
|
|
2454
2472
|
What a TTL sweep needs: the rows to archive, found through the
|
|
2455
2473
|
partial index on ``ttl_at`` (``idx_entries_ttl``) rather than by
|
|
2456
|
-
listing the tenant and checking each entry's clock in Python.
|
|
2457
|
-
|
|
2458
|
-
excluded —
|
|
2459
|
-
|
|
2474
|
+
listing the tenant and checking each entry's clock in Python. The
|
|
2475
|
+
scope is ``list()``'s — current versions, this branch, shared paths
|
|
2476
|
+
excluded — and the rows are the ones ``list()`` leaves out: those past
|
|
2477
|
+
their TTL and not yet archived. *limit* bounds one sweep; the next
|
|
2478
|
+
sweep picks up the rest.
|
|
2460
2479
|
"""
|
|
2461
2480
|
conditions, params = list_conditions(
|
|
2462
2481
|
self._namespace, None, include_superseded=False, branch=branch, scope=None, agent_id=None,
|
|
2482
|
+
include_expired=True,
|
|
2463
2483
|
)
|
|
2464
2484
|
conditions.append("ttl_at IS NOT NULL AND ttl_at <= %s")
|
|
2465
2485
|
params.append(now or datetime.now(timezone.utc))
|
|
@@ -2684,7 +2704,7 @@ class PostgresAdapter(AdapterABC):
|
|
|
2684
2704
|
|
|
2685
2705
|
# The branch scope is resolved on the connection below (it may need the
|
|
2686
2706
|
# branch's parent); a placeholder keeps its position in the WHERE.
|
|
2687
|
-
conditions = ["namespace = %s", _BRANCH_SCOPE, "superseded_at IS NULL"]
|
|
2707
|
+
conditions = ["namespace = %s", _BRANCH_SCOPE, "superseded_at IS NULL", _NOT_EXPIRED]
|
|
2688
2708
|
params: list[Any] = [self._namespace]
|
|
2689
2709
|
|
|
2690
2710
|
if query.depth < 3:
|
|
@@ -2821,7 +2841,7 @@ class PostgresAdapter(AdapterABC):
|
|
|
2821
2841
|
|
|
2822
2842
|
query_vec = query.embedding if query.embedding is not None else embedder.embed(query.text)
|
|
2823
2843
|
|
|
2824
|
-
conditions = ["namespace = %s", "superseded_at IS NULL", "embedding IS NOT NULL"]
|
|
2844
|
+
conditions = ["namespace = %s", "superseded_at IS NULL", "embedding IS NOT NULL", _NOT_EXPIRED]
|
|
2825
2845
|
params: list[Any] = [self._namespace]
|
|
2826
2846
|
|
|
2827
2847
|
if query.entity_path is not None:
|
|
@@ -2928,15 +2948,20 @@ class PostgresAdapter(AdapterABC):
|
|
|
2928
2948
|
belonging to another agent turns up in ``keys``. A NULL *agent_id*
|
|
2929
2949
|
compares as unknown rather than true, so passing nothing counts shared
|
|
2930
2950
|
entries only — the safe direction.
|
|
2951
|
+
|
|
2952
|
+
The TTL predicate is here for the same reason: ``list()`` leaves out an
|
|
2953
|
+
entry past its ``ttl_at``, so the key of one must not turn up in
|
|
2954
|
+
``keys`` either.
|
|
2931
2955
|
"""
|
|
2932
2956
|
with self._pool.connection() as conn:
|
|
2933
2957
|
row = conn.execute(
|
|
2934
|
-
"""SELECT count(*) AS total,
|
|
2958
|
+
f"""SELECT count(*) AS total,
|
|
2935
2959
|
count(*) FILTER (WHERE coalesce(recall_count, 0) = 0)
|
|
2936
2960
|
AS never_read
|
|
2937
2961
|
FROM amfs_memory_entries
|
|
2938
2962
|
WHERE namespace = %s AND branch = %s AND entity_path = %s
|
|
2939
2963
|
AND superseded_at IS NULL
|
|
2964
|
+
AND {_NOT_EXPIRED}
|
|
2940
2965
|
AND (%s::text IS NULL OR key <> %s)
|
|
2941
2966
|
AND (shared OR agent_id = %s::text)""",
|
|
2942
2967
|
(self._namespace, branch, entity_path, exclude_key, exclude_key,
|
|
@@ -2944,9 +2969,10 @@ class PostgresAdapter(AdapterABC):
|
|
|
2944
2969
|
).fetchone()
|
|
2945
2970
|
keys = [
|
|
2946
2971
|
r["key"] for r in conn.execute(
|
|
2947
|
-
"""SELECT key FROM amfs_memory_entries
|
|
2972
|
+
f"""SELECT key FROM amfs_memory_entries
|
|
2948
2973
|
WHERE namespace = %s AND branch = %s AND entity_path = %s
|
|
2949
2974
|
AND superseded_at IS NULL
|
|
2975
|
+
AND {_NOT_EXPIRED}
|
|
2950
2976
|
AND (%s::text IS NULL OR key <> %s)
|
|
2951
2977
|
AND (shared OR agent_id = %s::text)
|
|
2952
2978
|
ORDER BY key LIMIT %s""",
|
|
@@ -5086,6 +5112,7 @@ class PostgresAdapter(AdapterABC):
|
|
|
5086
5112
|
"agent_id = %s",
|
|
5087
5113
|
"superseded_at IS NULL",
|
|
5088
5114
|
"branch = 'main'",
|
|
5115
|
+
_NOT_EXPIRED,
|
|
5089
5116
|
]
|
|
5090
5117
|
params: list[Any] = [namespace, agent_id]
|
|
5091
5118
|
for prefix in exclude_prefixes:
|
{amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/async_adapter.py
RENAMED
|
@@ -42,6 +42,7 @@ from amfs_postgres.adapter import (
|
|
|
42
42
|
_EVIDENCE_COLUMNS,
|
|
43
43
|
_EVIDENCE_SELECT,
|
|
44
44
|
_EXCLUDE_SHARED_PATHS,
|
|
45
|
+
_NOT_EXPIRED,
|
|
45
46
|
_REUSE_EVENT_INSERT_SQL,
|
|
46
47
|
PostgresAdapter,
|
|
47
48
|
_evidence_params,
|
|
@@ -353,6 +354,12 @@ class AsyncPostgresAdapter:
|
|
|
353
354
|
entry = self._row_to_entry(row)
|
|
354
355
|
if entry.confidence < min_confidence:
|
|
355
356
|
return None
|
|
357
|
+
# As ``PostgresAdapter.read`` does: a TTL takes effect on read, without
|
|
358
|
+
# waiting for the sweep. Missing here, the HTTP read route — which
|
|
359
|
+
# tries this adapter first — served an expired entry that the sync
|
|
360
|
+
# adapter behind the same route would have withheld.
|
|
361
|
+
if entry.is_expired():
|
|
362
|
+
return None
|
|
356
363
|
return entry
|
|
357
364
|
|
|
358
365
|
# ──────────────────────────────────────────────────────────────
|
|
@@ -580,7 +587,7 @@ class AsyncPostgresAdapter:
|
|
|
580
587
|
|
|
581
588
|
# ``_BRANCH_SCOPE`` is swapped for the real branch condition once a
|
|
582
589
|
# connection is open; its params are spliced in after the namespace.
|
|
583
|
-
conditions = ["namespace = %s", _BRANCH_SCOPE, "superseded_at IS NULL"]
|
|
590
|
+
conditions = ["namespace = %s", _BRANCH_SCOPE, "superseded_at IS NULL", _NOT_EXPIRED]
|
|
584
591
|
params: list[Any] = [self._namespace]
|
|
585
592
|
|
|
586
593
|
if query.depth < 3:
|
|
@@ -713,6 +720,7 @@ class AsyncPostgresAdapter:
|
|
|
713
720
|
_BRANCH_SCOPE,
|
|
714
721
|
"superseded_at IS NULL",
|
|
715
722
|
"embedding IS NOT NULL",
|
|
723
|
+
_NOT_EXPIRED,
|
|
716
724
|
]
|
|
717
725
|
params: list[Any] = [self._namespace]
|
|
718
726
|
if query.entity_path is not None:
|
|
@@ -1145,15 +1153,19 @@ class AsyncPostgresAdapter:
|
|
|
1145
1153
|
``list()`` applied, and this block's ``keys`` land in a tool result. A
|
|
1146
1154
|
NULL *agent_id* compares as unknown, so passing nothing counts shared
|
|
1147
1155
|
entries only.
|
|
1156
|
+
|
|
1157
|
+
The TTL predicate is carried for the same reason: ``list()`` leaves out
|
|
1158
|
+
an entry past its ``ttl_at``, so its key must not turn up here either.
|
|
1148
1159
|
"""
|
|
1149
1160
|
async with self._pool.connection() as conn:
|
|
1150
1161
|
cur = await conn.execute(
|
|
1151
|
-
"""SELECT count(*) AS total,
|
|
1162
|
+
f"""SELECT count(*) AS total,
|
|
1152
1163
|
count(*) FILTER (WHERE coalesce(recall_count, 0) = 0)
|
|
1153
1164
|
AS never_read
|
|
1154
1165
|
FROM amfs_memory_entries
|
|
1155
1166
|
WHERE namespace = %s AND branch = %s AND entity_path = %s
|
|
1156
1167
|
AND superseded_at IS NULL
|
|
1168
|
+
AND {_NOT_EXPIRED}
|
|
1157
1169
|
AND (%s::text IS NULL OR key <> %s)
|
|
1158
1170
|
AND (shared OR agent_id = %s::text)""",
|
|
1159
1171
|
(self._namespace, branch, entity_path, exclude_key, exclude_key,
|
|
@@ -1161,9 +1173,10 @@ class AsyncPostgresAdapter:
|
|
|
1161
1173
|
)
|
|
1162
1174
|
row = await cur.fetchone()
|
|
1163
1175
|
cur = await conn.execute(
|
|
1164
|
-
"""SELECT key FROM amfs_memory_entries
|
|
1176
|
+
f"""SELECT key FROM amfs_memory_entries
|
|
1165
1177
|
WHERE namespace = %s AND branch = %s AND entity_path = %s
|
|
1166
1178
|
AND superseded_at IS NULL
|
|
1179
|
+
AND {_NOT_EXPIRED}
|
|
1167
1180
|
AND (%s::text IS NULL OR key <> %s)
|
|
1168
1181
|
AND (shared OR agent_id = %s::text)
|
|
1169
1182
|
ORDER BY key LIMIT %s""",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/evidence_sql.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/retention.py
RENAMED
|
File without changes
|
|
File without changes
|
{amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/tenant_context.py
RENAMED
|
File without changes
|
{amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/tenant_gucs.py
RENAMED
|
File without changes
|
{amfs_adapter_postgres-0.2.43 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/trace_partitions.py
RENAMED
|
File without changes
|