amfs-http-server 0.3.52__tar.gz → 0.3.54__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.
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: amfs-http-server
3
- Version: 0.3.52
3
+ Version: 0.3.54
4
4
  Summary: AMFS HTTP/REST API server with SSE support
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: >=3.11
7
- Requires-Dist: amfs-core>=0.3.43
8
- Requires-Dist: amfs>=0.5.35
7
+ Requires-Dist: amfs-core>=0.3.44
8
+ Requires-Dist: amfs>=0.5.36
9
9
  Requires-Dist: fastapi>=0.115
10
10
  Requires-Dist: httpx>=0.27
11
11
  Requires-Dist: sse-starlette>=2.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "amfs-http-server"
3
- version = "0.3.52"
3
+ version = "0.3.54"
4
4
  description = "AMFS HTTP/REST API server with SSE support"
5
5
  requires-python = ">=3.11"
6
6
  license = "Apache-2.0"
@@ -13,7 +13,7 @@ dependencies = [
13
13
  # this tree is what test_dependency_floors forbids.
14
14
  # 0.5.18 for AgentMemory.as_agent, which the agent detail page calls to list
15
15
  # an agent's entries as that agent rather than as the server.
16
- "amfs>=0.5.35",
16
+ "amfs>=0.5.36",
17
17
  # 0.3.23 is real in the silent way: the write path calls
18
18
  # adapter.scope_counts(), which is a default on AdapterABC from 0.3.23. On an
19
19
  # older core the adapters that do not override it — filesystem, S3 — have no
@@ -27,7 +27,7 @@ dependencies = [
27
27
  # unexpected keyword, _scope_block swallows it, and the block disappears —
28
28
  # while an adapter that ignored the argument instead would name another
29
29
  # agent's private keys. Either way the floor is what makes the rule real.
30
- "amfs-core>=0.3.43",
30
+ "amfs-core>=0.3.44",
31
31
  "fastapi>=0.115",
32
32
  # 0.37 is where uvicorn.run() grew timeout_worker_healthcheck, which
33
33
  # main() passes unconditionally; on anything older that is a TypeError
@@ -979,11 +979,11 @@ async def _branch_treatment(
979
979
  Retrieve's step 9b: what a read routed to a repair branch exists to put in
980
980
  front of the agent. Returns ``(entry, similarity, keyword)`` triples for
981
981
  the branch's added or modified entries that the query's semantic pool on
982
- the branch holds (no confidence gate), or that a lexical read on the
983
- branch returns where there are no vectors. Empty on ``main``, with no
984
- scope, on an adapter without branches, when the branch changed nothing
985
- under the scope, or when the query is about none of it. Never raises:
986
- a failure here leaves the ranking as it was.
982
+ the branch holds (no confidence gate, no evidence gate), or that a lexical
983
+ read on the branch returns where there are no vectors. Empty on ``main``,
984
+ with no scope, on an adapter without branches, when the branch changed
985
+ nothing under the scope, or when the query is about none of it. Never
986
+ raises: a failure here leaves the ranking as it was.
987
987
  """
988
988
  if not isinstance(branch, str) or branch.strip() in ("", "main") or not entity_path:
989
989
  return []
@@ -1048,10 +1048,19 @@ async def _branch_treatment(
1048
1048
  slot = found.get(ref)
1049
1049
  found[ref] = (entry, slot[1], 1.0) if slot else (entry, 0.0, 1.0)
1050
1050
 
1051
+ # Discredited or not: the branch's own changes are pinned whatever their
1052
+ # evidence says. The pin exists so the arm under test is *treated*; the
1053
+ # canary is what decides whether the treatment is good, and it can only
1054
+ # decide if the arm read it. Dropping a discredited entry here handed the
1055
+ # verdict to whatever had discredited it first — on clbench (2026-09-30,
1056
+ # fleet 10) the loop's own replays, within minutes of the write, before
1057
+ # the canary's first session in two of six cases; the arm then read its
1058
+ # fix in 0 of 74 sessions and one canary *failed* on an arm that had never
1059
+ # seen the treatment. The row keeps its ``evidence_status``, so the agent
1060
+ # sees the record; it is not hidden from the arm on the record's account.
1051
1061
  out = [
1052
1062
  t for t in found.values()
1053
- if getattr(t[0], "discredited_at", None) is None
1054
- and (include_artifacts or not is_artifact(t[0]))
1063
+ if include_artifacts or not is_artifact(t[0])
1055
1064
  ]
1056
1065
  if out and vis is not None and vis.should_filter():
1057
1066
  allowed = {e.entry_key for e in vis.filter_entries([t[0] for t in out])}
@@ -3442,11 +3451,17 @@ async def retrieve_entries(
3442
3451
  # business, not a reason to hide the treatment from its own arm), or
3443
3452
  # a lexical hit where there are no vectors; an entry the query is not
3444
3453
  # about stays where it is — a fix to the CI rule is not pinned onto a
3445
- # support ticket. Discredited entries are not pinned: a fix the record
3446
- # has already failed is the avoid list's, not the head's; nor is a
3447
- # procedure whose preconditions contradict the run's environment —
3448
- # it is named in ``_meta.not_applicable`` like any other. Main reads
3449
- # are untouched.
3454
+ # support ticket. A discredited entry is pinned all the same, with its
3455
+ # evidence on the row: the canary is the test of the fix, and it can
3456
+ # only test what the arm read. The first version of this step left
3457
+ # discredited entries to the avoid list; measured on clbench
3458
+ # (2026-09-30, fleet 10, six canaries), five corrections were
3459
+ # discredited within 1-24 minutes of being written — by the loop's
3460
+ # own replays, two before the canary's first session — and the arm
3461
+ # they were meant to treat read them in 0 of 74 sessions. What is not
3462
+ # pinned is a procedure whose preconditions contradict the run's
3463
+ # environment — it is named in ``_meta.not_applicable`` like any
3464
+ # other. Main reads are untouched.
3450
3465
  pinned = await _branch_treatment(
3451
3466
  branch=branch,
3452
3467
  entity_path=req.entity_path,
@@ -3509,6 +3524,9 @@ async def retrieve_entries(
3509
3524
  pinned_scored.sort(key=lambda t: t[1], reverse=True)
3510
3525
  pinned_keys = {e.entry_key for e, _, _ in pinned_scored}
3511
3526
  scored = pinned_scored + [t for t in scored if t[0].entry_key not in pinned_keys]
3527
+ # Once, at the head: a pinned entry the record has discredited is not
3528
+ # also handed back in the avoid list. The row carries its evidence.
3529
+ avoided = [e for e in avoided if e.entry_key not in pinned_keys]
3512
3530
 
3513
3531
  # 10. Reuse accounting. Semantic retrieval is the primary way memories
3514
3532
  # (e.g. browser-extension clips) get surfaced and used, but it
@@ -9418,11 +9436,18 @@ def _start_embedded_cortex() -> None:
9418
9436
  # adapter grew list_scopes(), but the knob stays: 0 disables it on
9419
9437
  # deployments where the event path is trusted to compile every scope.
9420
9438
  catchup_s = float(os.environ.get("AMFS_CORTEX_CATCHUP_INTERVAL_S", "300"))
9439
+ # Tier A consolidation walks every tenant entity by entity. One instance
9440
+ # of the fleet runs it per interval (advisory lock inside the worker);
9441
+ # 0 disables it on deployments that consolidate on demand instead.
9442
+ consolidation_s = float(
9443
+ os.environ.get("AMFS_CORTEX_CONSOLIDATION_INTERVAL_S", "21600")
9444
+ )
9421
9445
  worker = CortexWorker(
9422
9446
  dsn=dsn,
9423
9447
  compiler=compiler,
9424
9448
  use_advisory_lock=False,
9425
9449
  catchup_interval_s=catchup_s,
9450
+ consolidation_interval_s=consolidation_s,
9426
9451
  tenant_provider=tenant_provider,
9427
9452
  )
9428
9453