amfs-adapter-postgres 0.2.20__tar.gz → 0.2.22__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.20 → amfs_adapter_postgres-0.2.22}/PKG-INFO +2 -2
- {amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/pyproject.toml +2 -2
- {amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/adapter.py +122 -26
- {amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/.gitignore +0 -0
- {amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/__init__.py +0 -0
- {amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/_fts.py +0 -0
- {amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/async_adapter.py +0 -0
- {amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/knn.py +0 -0
- {amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/migrations/001_initial.sql +0 -0
- {amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/migrations/002_artifact_refs.sql +0 -0
- {amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/migrations/002_search_indexes.sql +0 -0
- {amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/migrations/003_knowledge_graph.sql +0 -0
- {amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/migrations/004_agent_groups.sql +0 -0
- {amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/migrations/005_configurable_embedding_dim.sql +0 -0
- {amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/migrations/006_is_artifact.sql +0 -0
- {amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/migrations/006_outcome_confidence_semantics.sql +0 -0
- {amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/migrations/007_outcome_propagation_row_copy.sql +0 -0
- {amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/migrations/008_outcome_evidence.sql +0 -0
- {amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/migrations/009_action_priors.sql +0 -0
- {amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/migrations/010_local_evidence.sql +0 -0
- {amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/migrations/011_outcome_task_text.sql +0 -0
- {amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/retention.py +0 -0
- {amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/schema.sql +0 -0
- {amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/tenant_context.py +0 -0
- {amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/tenant_gucs.py +0 -0
- {amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/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.22
|
|
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.34
|
|
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.22"
|
|
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.34",
|
|
11
11
|
"psycopg[binary]>=3.1,<4",
|
|
12
12
|
"psycopg-pool>=3.1",
|
|
13
13
|
]
|
|
@@ -1212,6 +1212,12 @@ class PostgresAdapter(AdapterABC):
|
|
|
1212
1212
|
ON amfs_memory_entries (namespace, entity_path)
|
|
1213
1213
|
WHERE tier <= 2 AND superseded_at IS NULL
|
|
1214
1214
|
""")
|
|
1215
|
+
# TTL sweep (list_expired): the few rows carrying a ttl_at, by expiry.
|
|
1216
|
+
cur.execute("""
|
|
1217
|
+
CREATE INDEX IF NOT EXISTS idx_entries_ttl
|
|
1218
|
+
ON amfs_memory_entries (ttl_at)
|
|
1219
|
+
WHERE ttl_at IS NOT NULL AND superseded_at IS NULL
|
|
1220
|
+
""")
|
|
1215
1221
|
# Outcome propagation (trigger v3): evidence columns on entries,
|
|
1216
1222
|
# attempts/final_action_index on outcomes, and the propagate function
|
|
1217
1223
|
# with its helpers. Applied from the migration file rather than a copy
|
|
@@ -2193,6 +2199,34 @@ class PostgresAdapter(AdapterABC):
|
|
|
2193
2199
|
|
|
2194
2200
|
return [self._row_to_entry(r) for r in rows]
|
|
2195
2201
|
|
|
2202
|
+
def list_expired(
|
|
2203
|
+
self, *, now: datetime | None = None, branch: str = "main", limit: int = 1000
|
|
2204
|
+
) -> list[MemoryEntry]:
|
|
2205
|
+
"""Current entries whose ``ttl_at`` has passed, oldest expiry first.
|
|
2206
|
+
|
|
2207
|
+
What a TTL sweep needs: the rows to archive, found through the
|
|
2208
|
+
partial index on ``ttl_at`` (``idx_entries_ttl``) rather than by
|
|
2209
|
+
listing the tenant and checking each entry's clock in Python. Same
|
|
2210
|
+
row set as ``list()`` — current versions, this branch, shared paths
|
|
2211
|
+
excluded — narrowed to the expired ones. *limit* bounds one sweep;
|
|
2212
|
+
the next sweep picks up the rest.
|
|
2213
|
+
"""
|
|
2214
|
+
conditions, params = list_conditions(
|
|
2215
|
+
self._namespace, None, include_superseded=False, branch=branch, scope=None, agent_id=None,
|
|
2216
|
+
)
|
|
2217
|
+
conditions.append("ttl_at IS NOT NULL AND ttl_at <= %s")
|
|
2218
|
+
params.append(now or datetime.now(timezone.utc))
|
|
2219
|
+
query = (
|
|
2220
|
+
f"SELECT {entry_select(self._has_is_artifact_col, self._has_validators_col)} "
|
|
2221
|
+
f"FROM amfs_memory_entries WHERE {' AND '.join(conditions)} ORDER BY ttl_at LIMIT %s"
|
|
2222
|
+
)
|
|
2223
|
+
params.append(limit)
|
|
2224
|
+
with self._pool.connection() as conn:
|
|
2225
|
+
with conn.cursor() as cur:
|
|
2226
|
+
cur.execute(query, params)
|
|
2227
|
+
rows = cur.fetchall()
|
|
2228
|
+
return [self._row_to_entry(r) for r in rows]
|
|
2229
|
+
|
|
2196
2230
|
def list_scopes(self, *, branch: str = "main") -> tuple[set[str], set[str]]:
|
|
2197
2231
|
"""The distinct ``(entity paths, agent ids)`` behind the current entries.
|
|
2198
2232
|
|
|
@@ -3933,18 +3967,36 @@ class PostgresAdapter(AdapterABC):
|
|
|
3933
3967
|
*,
|
|
3934
3968
|
per_key: int = 50,
|
|
3935
3969
|
min_similarity: float = 0.0,
|
|
3970
|
+
tau: float | None = None,
|
|
3971
|
+
min_weight: float | None = None,
|
|
3972
|
+
recent: int = 5,
|
|
3936
3973
|
) -> dict[str, dict[str, Any]]:
|
|
3937
3974
|
"""The outcome record of each entry in ``entry_keys``, restricted to
|
|
3938
3975
|
outcomes whose task was like the one embedded.
|
|
3939
3976
|
|
|
3940
|
-
Returns ``entry_key -> {"success", "failure", "n", "best_similarity"
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3977
|
+
Returns ``entry_key -> {"success", "failure", "n", "best_similarity",
|
|
3978
|
+
"recent"}``. ``success`` and ``failure`` are weighted counts, the
|
|
3979
|
+
weight of each outcome relative to the *nearest* one for that key:
|
|
3980
|
+
``exp(-(best - sim) / tau)``, the form ``neighbourhood_weights`` gives
|
|
3981
|
+
the action priors and for the same reason — under a retrieval
|
|
3982
|
+
embedder every task on an entity clears any absolute floor, but
|
|
3983
|
+
within one query the nearest outcomes are the same kind of task and
|
|
3984
|
+
the rest trail by a gap. An outcome 0.05 behind the best counts a
|
|
3985
|
+
fifth, one 0.1 behind counts 4%, and rows under ``min_weight`` are
|
|
3986
|
+
dropped rather than counted toward ``n``. ``tau`` and ``min_weight``
|
|
3987
|
+
default to the priors' constants. ``recent`` is the newest
|
|
3988
|
+
in-neighbourhood outcomes for the key, newest first, each ``{success,
|
|
3989
|
+
similarity, committed_at}`` — the order the sums cannot carry, which
|
|
3990
|
+
``locally_discredited`` reads. Keys with no nearby outcome are absent.
|
|
3991
|
+
|
|
3992
|
+
Terminal outcomes credit the row's ``causal_entry_keys``; the failed
|
|
3993
|
+
attempts inside ``attempts`` credit their own keys with a failure,
|
|
3994
|
+
exactly as the propagation trigger applies them to the pooled columns
|
|
3995
|
+
— so this is the pooled record, conditioned on the task, and nothing
|
|
3996
|
+
more. ``min_similarity`` is an absolute floor under the relative
|
|
3997
|
+
weighting: rows below it never enter, so a key whose only outcomes
|
|
3998
|
+
are on unrelated tasks has no local record rather than a full-weight
|
|
3999
|
+
one.
|
|
3948
4000
|
|
|
3949
4001
|
Bounded on both sides: ``per_key`` nearest outcomes per entry, over
|
|
3950
4002
|
the outcomes the GIN index on ``causal_entry_keys`` narrows to.
|
|
@@ -3952,18 +4004,28 @@ class PostgresAdapter(AdapterABC):
|
|
|
3952
4004
|
"""
|
|
3953
4005
|
if not self._has_outcome_embedding_col or not embedding or not entry_keys:
|
|
3954
4006
|
return {}
|
|
4007
|
+
from amfs_core.actions import PRIORS_NEIGHBOURHOOD_MIN_W, PRIORS_NEIGHBOURHOOD_TAU
|
|
4008
|
+
|
|
4009
|
+
tau_ = float(PRIORS_NEIGHBOURHOOD_TAU if tau is None else tau)
|
|
4010
|
+
min_w = float(PRIORS_NEIGHBOURHOOD_MIN_W if min_weight is None else min_weight)
|
|
3955
4011
|
vec = f"[{','.join(str(v) for v in embedding)}]"
|
|
3956
4012
|
keys = list(dict.fromkeys(entry_keys))
|
|
3957
4013
|
# Each row is unnested to (key, is_failure) pairs: the terminal outcome
|
|
3958
4014
|
# for every causal key, and a failure for every key inside a failed
|
|
3959
4015
|
# attempt. Rows are narrowed first by key overlap (indexed), then
|
|
3960
|
-
# ranked per key by task similarity and cut at per_key
|
|
3961
|
-
#
|
|
3962
|
-
#
|
|
3963
|
-
#
|
|
4016
|
+
# ranked per key by task similarity and cut at per_key, then weighted
|
|
4017
|
+
# against the key's best match and cut again at min_weight. ``n``
|
|
4018
|
+
# counts distinct outcomes, not credits: one outcome that names a key
|
|
4019
|
+
# in a failed attempt and again as a causal key is one nearby task,
|
|
4020
|
+
# and must not on its own clear LOCAL_EVIDENCE_MIN_N. ``recent`` is
|
|
4021
|
+
# aggregated newest first with the failure credit ahead of the success
|
|
4022
|
+
# credit of the same outcome, so the Python dedupe below keeps the
|
|
4023
|
+
# failure: the entry misled an attempt on that task, whatever else it
|
|
4024
|
+
# did afterwards.
|
|
3964
4025
|
sql = """
|
|
3965
4026
|
WITH near AS (
|
|
3966
4027
|
SELECT o.id AS outcome_id, o.outcome_type, o.causal_entry_keys, o.attempts,
|
|
4028
|
+
o.committed_at,
|
|
3967
4029
|
1 - (o.task_embedding <=> %s::vector) AS similarity
|
|
3968
4030
|
FROM amfs_outcomes o
|
|
3969
4031
|
WHERE o.namespace = %s
|
|
@@ -3983,11 +4045,11 @@ class PostgresAdapter(AdapterABC):
|
|
|
3983
4045
|
credited AS (
|
|
3984
4046
|
SELECT n.outcome_id, k AS entry_key,
|
|
3985
4047
|
amfs_outcome_is_success(n.outcome_type) AS success,
|
|
3986
|
-
n.similarity
|
|
4048
|
+
n.similarity, n.committed_at
|
|
3987
4049
|
FROM near n, unnest(n.causal_entry_keys) AS k
|
|
3988
4050
|
WHERE k = ANY(%s::text[])
|
|
3989
4051
|
UNION ALL
|
|
3990
|
-
SELECT n.outcome_id, ak AS entry_key, FALSE AS success, n.similarity
|
|
4052
|
+
SELECT n.outcome_id, ak AS entry_key, FALSE AS success, n.similarity, n.committed_at
|
|
3991
4053
|
FROM near n,
|
|
3992
4054
|
jsonb_array_elements(n.attempts) AS att,
|
|
3993
4055
|
jsonb_array_elements_text(
|
|
@@ -3996,34 +4058,68 @@ class PostgresAdapter(AdapterABC):
|
|
|
3996
4058
|
WHERE ak = ANY(%s::text[])
|
|
3997
4059
|
),
|
|
3998
4060
|
ranked AS (
|
|
3999
|
-
SELECT outcome_id, entry_key, success, similarity,
|
|
4000
|
-
row_number() OVER (PARTITION BY entry_key ORDER BY similarity DESC) AS rn
|
|
4061
|
+
SELECT outcome_id, entry_key, success, similarity, committed_at,
|
|
4062
|
+
row_number() OVER (PARTITION BY entry_key ORDER BY similarity DESC) AS rn,
|
|
4063
|
+
MAX(similarity) OVER (PARTITION BY entry_key) AS best
|
|
4001
4064
|
FROM credited
|
|
4002
4065
|
WHERE similarity >= %s
|
|
4066
|
+
),
|
|
4067
|
+
weighted AS (
|
|
4068
|
+
SELECT outcome_id, entry_key, success, similarity, committed_at, best,
|
|
4069
|
+
exp(-(best - similarity) / %s) AS w
|
|
4070
|
+
FROM ranked
|
|
4071
|
+
WHERE rn <= %s
|
|
4003
4072
|
)
|
|
4004
4073
|
SELECT entry_key,
|
|
4005
|
-
SUM(CASE WHEN success THEN
|
|
4006
|
-
SUM(CASE WHEN success THEN 0 ELSE
|
|
4074
|
+
SUM(CASE WHEN success THEN w ELSE 0 END) AS success,
|
|
4075
|
+
SUM(CASE WHEN success THEN 0 ELSE w END) AS failure,
|
|
4007
4076
|
COUNT(DISTINCT outcome_id) AS n,
|
|
4008
|
-
MAX(similarity) AS best_similarity
|
|
4009
|
-
|
|
4010
|
-
|
|
4077
|
+
MAX(similarity) AS best_similarity,
|
|
4078
|
+
(array_agg(
|
|
4079
|
+
jsonb_build_object(
|
|
4080
|
+
'o', outcome_id, 's', success, 'sim', similarity,
|
|
4081
|
+
'at', to_char(committed_at AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"+00:00"')
|
|
4082
|
+
)
|
|
4083
|
+
ORDER BY committed_at DESC, success ASC
|
|
4084
|
+
))[1:%s] AS recent
|
|
4085
|
+
FROM weighted
|
|
4086
|
+
WHERE w >= %s
|
|
4011
4087
|
GROUP BY entry_key
|
|
4012
4088
|
"""
|
|
4013
|
-
|
|
4089
|
+
# ``recent`` is over-fetched (two credits per outcome at most) and
|
|
4090
|
+
# deduped per outcome below.
|
|
4091
|
+
params = [
|
|
4092
|
+
vec, self._namespace, keys, keys, keys, keys, float(min_similarity),
|
|
4093
|
+
tau_ if tau_ > 0 else 1e-9, int(per_key), int(max(1, recent)) * 2, min_w,
|
|
4094
|
+
]
|
|
4014
4095
|
with self._pool.connection() as conn:
|
|
4015
4096
|
with conn.cursor() as cur:
|
|
4016
4097
|
cur.execute(sql, params)
|
|
4017
4098
|
rows = cur.fetchall()
|
|
4018
|
-
|
|
4019
|
-
|
|
4099
|
+
out: dict[str, dict[str, Any]] = {}
|
|
4100
|
+
for row in rows:
|
|
4101
|
+
seen: set[Any] = set()
|
|
4102
|
+
recent_rows: list[dict[str, Any]] = []
|
|
4103
|
+
for item in _jsonb(row.get("recent"), []) or []:
|
|
4104
|
+
item = _jsonb(item, None) # jsonb[] elements may arrive decoded or as text
|
|
4105
|
+
if not isinstance(item, dict) or item.get("o") in seen:
|
|
4106
|
+
continue
|
|
4107
|
+
seen.add(item.get("o"))
|
|
4108
|
+
recent_rows.append({
|
|
4109
|
+
"success": bool(item.get("s")),
|
|
4110
|
+
"similarity": float(item.get("sim") or 0.0),
|
|
4111
|
+
"committed_at": item.get("at"),
|
|
4112
|
+
})
|
|
4113
|
+
if len(recent_rows) >= max(1, recent):
|
|
4114
|
+
break
|
|
4115
|
+
out[row["entry_key"]] = {
|
|
4020
4116
|
"success": float(row["success"] or 0.0),
|
|
4021
4117
|
"failure": float(row["failure"] or 0.0),
|
|
4022
4118
|
"n": int(row["n"] or 0),
|
|
4023
4119
|
"best_similarity": float(row["best_similarity"] or 0.0),
|
|
4120
|
+
"recent": recent_rows,
|
|
4024
4121
|
}
|
|
4025
|
-
|
|
4026
|
-
}
|
|
4122
|
+
return out
|
|
4027
4123
|
|
|
4028
4124
|
def action_stats(
|
|
4029
4125
|
self,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/async_adapter.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
|
{amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/retention.py
RENAMED
|
File without changes
|
|
File without changes
|
{amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/tenant_context.py
RENAMED
|
File without changes
|
{amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/tenant_gucs.py
RENAMED
|
File without changes
|
{amfs_adapter_postgres-0.2.20 → amfs_adapter_postgres-0.2.22}/src/amfs_postgres/trace_partitions.py
RENAMED
|
File without changes
|