amfs-adapter-postgres 0.2.42__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.42 → amfs_adapter_postgres-0.2.44}/PKG-INFO +2 -2
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/pyproject.toml +2 -2
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/adapter.py +64 -12
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/async_adapter.py +16 -3
- amfs_adapter_postgres-0.2.44/src/amfs_postgres/migrations/014_outcome_covariates.sql +20 -0
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/.gitignore +0 -0
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/__init__.py +0 -0
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/_fts.py +0 -0
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/evidence_sql.py +0 -0
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/knn.py +0 -0
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/001_initial.sql +0 -0
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/002_artifact_refs.sql +0 -0
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/002_search_indexes.sql +0 -0
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/003_knowledge_graph.sql +0 -0
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/004_agent_groups.sql +0 -0
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/005_configurable_embedding_dim.sql +0 -0
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/006_is_artifact.sql +0 -0
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/006_outcome_confidence_semantics.sql +0 -0
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/007_outcome_propagation_row_copy.sql +0 -0
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/008_outcome_evidence.sql +0 -0
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/009_action_priors.sql +0 -0
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/010_local_evidence.sql +0 -0
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/011_outcome_task_text.sql +0 -0
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/012_evidence_from_model.sql +0 -0
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/migrations/013_outcome_verified_by_backfill.sql +0 -0
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/retention.py +0 -0
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/schema.sql +0 -0
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/tenant_context.py +0 -0
- {amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/tenant_gucs.py +0 -0
- {amfs_adapter_postgres-0.2.42 → 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
|
]
|
|
@@ -34,7 +34,7 @@ from amfs_core.exclusions import (
|
|
|
34
34
|
AGENT_ID_NOT_EXCLUDED_SQL,
|
|
35
35
|
ENTITY_PATH_NOT_EXCLUDED_SQL,
|
|
36
36
|
)
|
|
37
|
-
from amfs_core.actions import entity_paths_of, recorded_environment
|
|
37
|
+
from amfs_core.actions import entity_paths_of, recorded_covariates, recorded_environment
|
|
38
38
|
from amfs_core.evidence import cited_entries, outcome_model, outcome_read_keys, outcome_verified_by
|
|
39
39
|
from amfs_core.models import (
|
|
40
40
|
OUTCOME_MULTIPLIERS,
|
|
@@ -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)
|
|
@@ -313,6 +331,10 @@ _LOCAL_EVIDENCE_SQL = (
|
|
|
313
331
|
_OUTCOME_TASK_TEXT_SQL = (
|
|
314
332
|
Path(__file__).parent / "migrations" / "011_outcome_task_text.sql"
|
|
315
333
|
).read_text(encoding="utf-8")
|
|
334
|
+
#: Migration 014 — the covariates of the task on the outcome row.
|
|
335
|
+
_OUTCOME_COVARIATES_SQL = (
|
|
336
|
+
Path(__file__).parent / "migrations" / "014_outcome_covariates.sql"
|
|
337
|
+
).read_text(encoding="utf-8")
|
|
316
338
|
#: Migration 012 — the evidence functions rendered from ``amfs_core.evidence``
|
|
317
339
|
#: (``evidence_sql.render_evidence_sql``), replacing the hand-written bodies
|
|
318
340
|
#: of 008/009 with the same arithmetic read off the Python model. Applied
|
|
@@ -864,6 +886,7 @@ class PostgresAdapter(AdapterABC):
|
|
|
864
886
|
self._has_outcome_embedding_col = False
|
|
865
887
|
self._has_outcome_env_col = False
|
|
866
888
|
self._has_task_text_col = False
|
|
889
|
+
self._has_covariates_col = False
|
|
867
890
|
self._has_verified_by_col = False
|
|
868
891
|
self._pgvector_version: tuple[int, ...] | None = None
|
|
869
892
|
# When an embedder is provided, embeddings are computed at write time and
|
|
@@ -1375,6 +1398,12 @@ class PostgresAdapter(AdapterABC):
|
|
|
1375
1398
|
ALTER TABLE amfs_outcomes
|
|
1376
1399
|
ADD COLUMN IF NOT EXISTS environment JSONB
|
|
1377
1400
|
""")
|
|
1401
|
+
# The covariates of the task — the run's declared attributes other
|
|
1402
|
+
# than its environment and situation (the customer's plan, the app
|
|
1403
|
+
# version a lookup showed) — so a contested situation's record can be
|
|
1404
|
+
# split by what separates the runs the old action still wins from the
|
|
1405
|
+
# ones it lost (amfs_core.actions.split_contested). See migration 014.
|
|
1406
|
+
cur.execute(_OUTCOME_COVARIATES_SQL)
|
|
1378
1407
|
# Query-conditioned evidence: see migration 010.
|
|
1379
1408
|
cur.execute(_LOCAL_EVIDENCE_SQL)
|
|
1380
1409
|
# The task text on outcomes: see migration 011.
|
|
@@ -1845,7 +1874,7 @@ class PostgresAdapter(AdapterABC):
|
|
|
1845
1874
|
WHERE table_name = 'amfs_outcomes'
|
|
1846
1875
|
AND column_name IN (
|
|
1847
1876
|
'actions_taken', 'task_embedding', 'environment', 'task_text',
|
|
1848
|
-
'verified_by'
|
|
1877
|
+
'verified_by', 'covariates'
|
|
1849
1878
|
)
|
|
1850
1879
|
""",
|
|
1851
1880
|
)
|
|
@@ -1855,6 +1884,7 @@ class PostgresAdapter(AdapterABC):
|
|
|
1855
1884
|
self._has_outcome_env_col = "environment" in found
|
|
1856
1885
|
self._has_task_text_col = "task_text" in found
|
|
1857
1886
|
self._has_verified_by_col = "verified_by" in found
|
|
1887
|
+
self._has_covariates_col = "covariates" in found
|
|
1858
1888
|
|
|
1859
1889
|
# ------------------------------------------------------------------
|
|
1860
1890
|
# read
|
|
@@ -2441,13 +2471,15 @@ class PostgresAdapter(AdapterABC):
|
|
|
2441
2471
|
|
|
2442
2472
|
What a TTL sweep needs: the rows to archive, found through the
|
|
2443
2473
|
partial index on ``ttl_at`` (``idx_entries_ttl``) rather than by
|
|
2444
|
-
listing the tenant and checking each entry's clock in Python.
|
|
2445
|
-
|
|
2446
|
-
excluded —
|
|
2447
|
-
|
|
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.
|
|
2448
2479
|
"""
|
|
2449
2480
|
conditions, params = list_conditions(
|
|
2450
2481
|
self._namespace, None, include_superseded=False, branch=branch, scope=None, agent_id=None,
|
|
2482
|
+
include_expired=True,
|
|
2451
2483
|
)
|
|
2452
2484
|
conditions.append("ttl_at IS NOT NULL AND ttl_at <= %s")
|
|
2453
2485
|
params.append(now or datetime.now(timezone.utc))
|
|
@@ -2672,7 +2704,7 @@ class PostgresAdapter(AdapterABC):
|
|
|
2672
2704
|
|
|
2673
2705
|
# The branch scope is resolved on the connection below (it may need the
|
|
2674
2706
|
# branch's parent); a placeholder keeps its position in the WHERE.
|
|
2675
|
-
conditions = ["namespace = %s", _BRANCH_SCOPE, "superseded_at IS NULL"]
|
|
2707
|
+
conditions = ["namespace = %s", _BRANCH_SCOPE, "superseded_at IS NULL", _NOT_EXPIRED]
|
|
2676
2708
|
params: list[Any] = [self._namespace]
|
|
2677
2709
|
|
|
2678
2710
|
if query.depth < 3:
|
|
@@ -2809,7 +2841,7 @@ class PostgresAdapter(AdapterABC):
|
|
|
2809
2841
|
|
|
2810
2842
|
query_vec = query.embedding if query.embedding is not None else embedder.embed(query.text)
|
|
2811
2843
|
|
|
2812
|
-
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]
|
|
2813
2845
|
params: list[Any] = [self._namespace]
|
|
2814
2846
|
|
|
2815
2847
|
if query.entity_path is not None:
|
|
@@ -2916,15 +2948,20 @@ class PostgresAdapter(AdapterABC):
|
|
|
2916
2948
|
belonging to another agent turns up in ``keys``. A NULL *agent_id*
|
|
2917
2949
|
compares as unknown rather than true, so passing nothing counts shared
|
|
2918
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.
|
|
2919
2955
|
"""
|
|
2920
2956
|
with self._pool.connection() as conn:
|
|
2921
2957
|
row = conn.execute(
|
|
2922
|
-
"""SELECT count(*) AS total,
|
|
2958
|
+
f"""SELECT count(*) AS total,
|
|
2923
2959
|
count(*) FILTER (WHERE coalesce(recall_count, 0) = 0)
|
|
2924
2960
|
AS never_read
|
|
2925
2961
|
FROM amfs_memory_entries
|
|
2926
2962
|
WHERE namespace = %s AND branch = %s AND entity_path = %s
|
|
2927
2963
|
AND superseded_at IS NULL
|
|
2964
|
+
AND {_NOT_EXPIRED}
|
|
2928
2965
|
AND (%s::text IS NULL OR key <> %s)
|
|
2929
2966
|
AND (shared OR agent_id = %s::text)""",
|
|
2930
2967
|
(self._namespace, branch, entity_path, exclude_key, exclude_key,
|
|
@@ -2932,9 +2969,10 @@ class PostgresAdapter(AdapterABC):
|
|
|
2932
2969
|
).fetchone()
|
|
2933
2970
|
keys = [
|
|
2934
2971
|
r["key"] for r in conn.execute(
|
|
2935
|
-
"""SELECT key FROM amfs_memory_entries
|
|
2972
|
+
f"""SELECT key FROM amfs_memory_entries
|
|
2936
2973
|
WHERE namespace = %s AND branch = %s AND entity_path = %s
|
|
2937
2974
|
AND superseded_at IS NULL
|
|
2975
|
+
AND {_NOT_EXPIRED}
|
|
2938
2976
|
AND (%s::text IS NULL OR key <> %s)
|
|
2939
2977
|
AND (shared OR agent_id = %s::text)
|
|
2940
2978
|
ORDER BY key LIMIT %s""",
|
|
@@ -4248,6 +4286,15 @@ class PostgresAdapter(AdapterABC):
|
|
|
4248
4286
|
columns.append("environment")
|
|
4249
4287
|
placeholders.append("%s::jsonb")
|
|
4250
4288
|
params.append(json.dumps(env))
|
|
4289
|
+
if getattr(self, "_has_covariates_col", False):
|
|
4290
|
+
covariates = recorded_covariates({
|
|
4291
|
+
"covariates": record.covariates or None,
|
|
4292
|
+
"session_metadata": record.session_metadata or {},
|
|
4293
|
+
})
|
|
4294
|
+
if covariates:
|
|
4295
|
+
columns.append("covariates")
|
|
4296
|
+
placeholders.append("%s::jsonb")
|
|
4297
|
+
params.append(json.dumps(covariates, default=str))
|
|
4251
4298
|
task_embedding = self._outcome_embedding(record)
|
|
4252
4299
|
if task_embedding is not None:
|
|
4253
4300
|
columns.append("task_embedding")
|
|
@@ -4556,9 +4603,10 @@ class PostgresAdapter(AdapterABC):
|
|
|
4556
4603
|
conditions.append("committed_at >= %s")
|
|
4557
4604
|
params.append(since)
|
|
4558
4605
|
env_col = ", environment" if getattr(self, "_has_outcome_env_col", False) else ""
|
|
4606
|
+
cov_col = ", covariates" if getattr(self, "_has_covariates_col", False) else ""
|
|
4559
4607
|
sql = f"""
|
|
4560
4608
|
SELECT outcome_ref, outcome_type, committed_at, agent_id, actions_taken, situation,
|
|
4561
|
-
1 - (task_embedding <=> %s::vector) AS similarity{env_col}
|
|
4609
|
+
1 - (task_embedding <=> %s::vector) AS similarity{env_col}{cov_col}
|
|
4562
4610
|
FROM amfs_outcomes
|
|
4563
4611
|
WHERE {" AND ".join(conditions)}
|
|
4564
4612
|
ORDER BY task_embedding <=> %s::vector
|
|
@@ -4581,6 +4629,7 @@ class PostgresAdapter(AdapterABC):
|
|
|
4581
4629
|
"situation": row.get("situation"),
|
|
4582
4630
|
"actions_taken": _jsonb(row.get("actions_taken"), []),
|
|
4583
4631
|
"environment": _jsonb(row.get("environment"), {}) or {},
|
|
4632
|
+
"covariates": _jsonb(row.get("covariates"), {}) or {},
|
|
4584
4633
|
"similarity": sim,
|
|
4585
4634
|
})
|
|
4586
4635
|
return out
|
|
@@ -4788,8 +4837,9 @@ class PostgresAdapter(AdapterABC):
|
|
|
4788
4837
|
conditions.append("committed_at >= %s")
|
|
4789
4838
|
params.append(since)
|
|
4790
4839
|
env_col = ", environment" if getattr(self, "_has_outcome_env_col", False) else ""
|
|
4840
|
+
cov_col = ", covariates" if getattr(self, "_has_covariates_col", False) else ""
|
|
4791
4841
|
sql = f"""
|
|
4792
|
-
SELECT outcome_ref, outcome_type, committed_at, agent_id, actions_taken, situation{env_col}
|
|
4842
|
+
SELECT outcome_ref, outcome_type, committed_at, agent_id, actions_taken, situation{env_col}{cov_col}
|
|
4793
4843
|
FROM amfs_outcomes
|
|
4794
4844
|
WHERE {" AND ".join(conditions)}
|
|
4795
4845
|
ORDER BY committed_at DESC
|
|
@@ -4807,6 +4857,7 @@ class PostgresAdapter(AdapterABC):
|
|
|
4807
4857
|
"situation": row.get("situation"),
|
|
4808
4858
|
"actions_taken": _jsonb(row.get("actions_taken"), []),
|
|
4809
4859
|
"environment": _jsonb(row.get("environment"), {}) or {},
|
|
4860
|
+
"covariates": _jsonb(row.get("covariates"), {}) or {},
|
|
4810
4861
|
"similarity": 1.0,
|
|
4811
4862
|
} for row in rows]
|
|
4812
4863
|
|
|
@@ -5061,6 +5112,7 @@ class PostgresAdapter(AdapterABC):
|
|
|
5061
5112
|
"agent_id = %s",
|
|
5062
5113
|
"superseded_at IS NULL",
|
|
5063
5114
|
"branch = 'main'",
|
|
5115
|
+
_NOT_EXPIRED,
|
|
5064
5116
|
]
|
|
5065
5117
|
params: list[Any] = [namespace, agent_id]
|
|
5066
5118
|
for prefix in exclude_prefixes:
|
{amfs_adapter_postgres-0.2.42 → 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""",
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
-- Migration 014: the covariates of the task on the outcome row
|
|
2
|
+
--
|
|
3
|
+
-- An outcome records what was done (`actions_taken`) in what kind of task
|
|
4
|
+
-- (`situation`) under what runtime (`environment`). It did not record what
|
|
5
|
+
-- the task *was like*: the customer's plan, the app version the lookup
|
|
6
|
+
-- showed, the card's issuer country. When a rule changes for part of a
|
|
7
|
+
-- situation — the export fix stops working on paid plans and keeps working
|
|
8
|
+
-- on the free one — the situation's record turns contested (wins and losses
|
|
9
|
+
-- interleaved, no streak) and nothing in it says which runs the old action
|
|
10
|
+
-- still wins. On the 2026-10-21 regime study the recommendation kept the
|
|
11
|
+
-- stale action as `act` on 59 % of the first searches after such a change.
|
|
12
|
+
--
|
|
13
|
+
-- The covariates are the scalar attributes a run declared about the task
|
|
14
|
+
-- (`set_session_attributes`, `Run.begin(covariates=...)`) less its
|
|
15
|
+
-- environment keys and the situation label, copied here at commit from the
|
|
16
|
+
-- record so `amfs_core.actions.split_contested` can read them off the same
|
|
17
|
+
-- rows the priors are aggregated from. Scalars only; at most the attribute
|
|
18
|
+
-- bag's 20 keys. Written only when the record has any.
|
|
19
|
+
|
|
20
|
+
ALTER TABLE amfs_outcomes ADD COLUMN IF NOT EXISTS covariates JSONB;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{amfs_adapter_postgres-0.2.42 → 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
|
{amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/retention.py
RENAMED
|
File without changes
|
|
File without changes
|
{amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/tenant_context.py
RENAMED
|
File without changes
|
{amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/tenant_gucs.py
RENAMED
|
File without changes
|
{amfs_adapter_postgres-0.2.42 → amfs_adapter_postgres-0.2.44}/src/amfs_postgres/trace_partitions.py
RENAMED
|
File without changes
|