amfs-adapter-postgres 0.2.41__tar.gz → 0.2.42__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.41 → amfs_adapter_postgres-0.2.42}/PKG-INFO +1 -1
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/pyproject.toml +1 -1
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/adapter.py +123 -0
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/.gitignore +0 -0
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/__init__.py +0 -0
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/_fts.py +0 -0
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/async_adapter.py +0 -0
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/evidence_sql.py +0 -0
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/knn.py +0 -0
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/migrations/001_initial.sql +0 -0
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/migrations/002_artifact_refs.sql +0 -0
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/migrations/002_search_indexes.sql +0 -0
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/migrations/003_knowledge_graph.sql +0 -0
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/migrations/004_agent_groups.sql +0 -0
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/migrations/005_configurable_embedding_dim.sql +0 -0
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/migrations/006_is_artifact.sql +0 -0
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/migrations/006_outcome_confidence_semantics.sql +0 -0
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/migrations/007_outcome_propagation_row_copy.sql +0 -0
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/migrations/008_outcome_evidence.sql +0 -0
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/migrations/009_action_priors.sql +0 -0
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/migrations/010_local_evidence.sql +0 -0
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/migrations/011_outcome_task_text.sql +0 -0
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/migrations/012_evidence_from_model.sql +0 -0
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/migrations/013_outcome_verified_by_backfill.sql +0 -0
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/retention.py +0 -0
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/schema.sql +0 -0
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/tenant_context.py +0 -0
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/tenant_gucs.py +0 -0
- {amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/trace_partitions.py +0 -0
|
@@ -183,6 +183,45 @@ def _paginate_sql(
|
|
|
183
183
|
return query, params
|
|
184
184
|
|
|
185
185
|
|
|
186
|
+
def priority_score_sql(now: datetime, config: Any | None = None) -> tuple[str, list[Any]]:
|
|
187
|
+
"""``amfs_core.tiering.PriorityScorer.score`` as a SQL expression.
|
|
188
|
+
|
|
189
|
+
The tiers pages rank every current entry in the account by this score and
|
|
190
|
+
take the top 50 as Hot and the next 200 as Warm. Scoring in Python meant
|
|
191
|
+
loading the whole namespace first; on a 420K-entry account the two
|
|
192
|
+
requests the page makes together took an 8 GiB instance down. The
|
|
193
|
+
expression below is the same formula over the same columns, so the
|
|
194
|
+
database can order by it and hand back one page::
|
|
195
|
+
|
|
196
|
+
(alpha * importance + beta * recency) * freq_boost * time_decay
|
|
197
|
+
|
|
198
|
+
with ``importance = importance_score or confidence`` clamped to [0, 1],
|
|
199
|
+
``recency = exp(-lambda * age_days)``, ``freq_boost = max(1, ln(1 +
|
|
200
|
+
recall_count))`` and ``time_decay = exp(-lambda * age_days / freq_boost)``.
|
|
201
|
+
``importance_score`` of 0 falls through to confidence exactly as the
|
|
202
|
+
Python ``or`` does. Returns the expression and its parameters in placeholder
|
|
203
|
+
order: alpha, beta, lambda, *now*, lambda, *now*.
|
|
204
|
+
"""
|
|
205
|
+
if config is None:
|
|
206
|
+
from amfs_core.models import TierConfig
|
|
207
|
+
|
|
208
|
+
config = TierConfig()
|
|
209
|
+
age_days = "GREATEST(0.0, EXTRACT(EPOCH FROM (%s - written_at)) / 86400.0)"
|
|
210
|
+
importance = (
|
|
211
|
+
"LEAST(1.0, GREATEST(0.0, COALESCE(NULLIF(importance_score, 0), confidence)::float8))"
|
|
212
|
+
)
|
|
213
|
+
freq_boost = "GREATEST(1.0, LN(1.0 + COALESCE(recall_count, 0)))"
|
|
214
|
+
expr = (
|
|
215
|
+
f"((%s * {importance} + %s * EXP(-%s * {age_days}))"
|
|
216
|
+
f" * {freq_boost}"
|
|
217
|
+
f" * EXP(-%s * {age_days} / {freq_boost}))"
|
|
218
|
+
)
|
|
219
|
+
# The age expression appears twice, each with its own ``now``.
|
|
220
|
+
lam = float(config.decay_lambda)
|
|
221
|
+
params: list[Any] = [float(config.alpha), float(config.beta), lam, now, lam, now]
|
|
222
|
+
return expr, params
|
|
223
|
+
|
|
224
|
+
|
|
186
225
|
def list_conditions(
|
|
187
226
|
namespace: str,
|
|
188
227
|
entity_path: str | None,
|
|
@@ -2500,6 +2539,90 @@ class PostgresAdapter(AdapterABC):
|
|
|
2500
2539
|
row = cur.fetchone()
|
|
2501
2540
|
return int(row["n"]) if row else 0
|
|
2502
2541
|
|
|
2542
|
+
def priority_stats(
|
|
2543
|
+
self,
|
|
2544
|
+
*,
|
|
2545
|
+
scope: SqlScope | None = None,
|
|
2546
|
+
agent_id: str | None = None,
|
|
2547
|
+
now: datetime | None = None,
|
|
2548
|
+
branch: str = "main",
|
|
2549
|
+
) -> tuple[int, float | None]:
|
|
2550
|
+
"""``(count, mean priority score)`` over the rows :meth:`list` returns.
|
|
2551
|
+
|
|
2552
|
+
What the tiers distribution needs and nothing more: the count fixes
|
|
2553
|
+
the Hot/Warm/Archive split (the tiers are rank bands) and the mean is
|
|
2554
|
+
the page's "average score". Computed by the database with
|
|
2555
|
+
:func:`priority_score_sql`; no entry row leaves it.
|
|
2556
|
+
"""
|
|
2557
|
+
now = now or datetime.now(timezone.utc)
|
|
2558
|
+
score, score_params = priority_score_sql(now)
|
|
2559
|
+
with self._pool.connection() as conn:
|
|
2560
|
+
with conn.cursor() as cur:
|
|
2561
|
+
conditions, params = list_conditions(
|
|
2562
|
+
self._namespace,
|
|
2563
|
+
None,
|
|
2564
|
+
include_superseded=False,
|
|
2565
|
+
branch=branch,
|
|
2566
|
+
scope=scope,
|
|
2567
|
+
agent_id=agent_id,
|
|
2568
|
+
branch_scope=self._branch_scope(cur, branch),
|
|
2569
|
+
)
|
|
2570
|
+
where = " AND ".join(conditions)
|
|
2571
|
+
cur.execute(
|
|
2572
|
+
f"SELECT COUNT(*) AS n, AVG({score}) AS mean "
|
|
2573
|
+
f"FROM amfs_memory_entries WHERE {where}",
|
|
2574
|
+
[*score_params, *params],
|
|
2575
|
+
)
|
|
2576
|
+
row = cur.fetchone()
|
|
2577
|
+
if not row or not row["n"]:
|
|
2578
|
+
return 0, None
|
|
2579
|
+
mean = row["mean"]
|
|
2580
|
+
return int(row["n"]), (float(mean) if mean is not None else None)
|
|
2581
|
+
|
|
2582
|
+
def list_by_priority(
|
|
2583
|
+
self,
|
|
2584
|
+
*,
|
|
2585
|
+
scope: SqlScope | None = None,
|
|
2586
|
+
agent_id: str | None = None,
|
|
2587
|
+
limit: int,
|
|
2588
|
+
offset: int = 0,
|
|
2589
|
+
now: datetime | None = None,
|
|
2590
|
+
branch: str = "main",
|
|
2591
|
+
) -> list[MemoryEntry]:
|
|
2592
|
+
"""One page of the rows :meth:`list` returns, highest priority first.
|
|
2593
|
+
|
|
2594
|
+
The order is :func:`priority_score_sql` descending with the stable
|
|
2595
|
+
``written_at DESC, entity_path, key`` tiebreak, so a page boundary is
|
|
2596
|
+
deterministic. A tier is a band of this ranking: rows 0–49 are Hot,
|
|
2597
|
+
50–249 Warm, the rest Archive — the caller picks *offset* and *limit*
|
|
2598
|
+
from the band and the database returns only that page.
|
|
2599
|
+
"""
|
|
2600
|
+
now = now or datetime.now(timezone.utc)
|
|
2601
|
+
score, score_params = priority_score_sql(now)
|
|
2602
|
+
with self._pool.connection() as conn:
|
|
2603
|
+
with conn.cursor() as cur:
|
|
2604
|
+
conditions, params = list_conditions(
|
|
2605
|
+
self._namespace,
|
|
2606
|
+
None,
|
|
2607
|
+
include_superseded=False,
|
|
2608
|
+
branch=branch,
|
|
2609
|
+
scope=scope,
|
|
2610
|
+
agent_id=agent_id,
|
|
2611
|
+
branch_scope=self._branch_scope(cur, branch),
|
|
2612
|
+
)
|
|
2613
|
+
where = " AND ".join(conditions)
|
|
2614
|
+
query = (
|
|
2615
|
+
f"SELECT {entry_select(self._has_is_artifact_col, self._has_validators_col)} "
|
|
2616
|
+
f"FROM amfs_memory_entries WHERE {where} "
|
|
2617
|
+
f"ORDER BY {score} DESC, written_at DESC, entity_path, key"
|
|
2618
|
+
)
|
|
2619
|
+
query, all_params = _paginate_sql(
|
|
2620
|
+
query, [*params, *score_params], limit=limit, offset=offset
|
|
2621
|
+
)
|
|
2622
|
+
cur.execute(query, all_params)
|
|
2623
|
+
rows = cur.fetchall()
|
|
2624
|
+
return [self._row_to_entry(r) for r in rows]
|
|
2625
|
+
|
|
2503
2626
|
def entry_authors(self, refs: list[tuple[str, str]]) -> dict[tuple[str, str], str]:
|
|
2504
2627
|
"""Who wrote each ``(entity_path, key)``, for the current version.
|
|
2505
2628
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/async_adapter.py
RENAMED
|
File without changes
|
{amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/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.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/retention.py
RENAMED
|
File without changes
|
|
File without changes
|
{amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/tenant_context.py
RENAMED
|
File without changes
|
{amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/tenant_gucs.py
RENAMED
|
File without changes
|
{amfs_adapter_postgres-0.2.41 → amfs_adapter_postgres-0.2.42}/src/amfs_postgres/trace_partitions.py
RENAMED
|
File without changes
|