amfs-http-server 0.3.44__tar.gz → 0.3.45__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,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: amfs-http-server
3
- Version: 0.3.44
3
+ Version: 0.3.45
4
4
  Summary: AMFS HTTP/REST API server with SSE support
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: >=3.11
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "amfs-http-server"
3
- version = "0.3.44"
3
+ version = "0.3.45"
4
4
  description = "AMFS HTTP/REST API server with SSE support"
5
5
  requires-python = ">=3.11"
6
6
  license = "Apache-2.0"
@@ -1459,6 +1459,12 @@ async def _list_agent_entries_page(
1459
1459
  )
1460
1460
 
1461
1461
 
1462
+ # How many distinct entity paths the memory-graph page will list to name the
1463
+ # authors of what an agent read. One indexed query per path; an agent that has
1464
+ # read across more paths than this gets the first N and ``truncated: true``.
1465
+ _MEMORY_GRAPH_MAX_READ_PATHS = 200
1466
+
1467
+
1462
1468
  def _bounded_scan(items: list[Any], ceiling: int) -> tuple[list[Any], bool]:
1463
1469
  """Trim a scan to *ceiling* rows and say whether anything was cut.
1464
1470
 
@@ -5526,18 +5532,36 @@ def agent_memory_graph(
5526
5532
  raise HTTPException(status_code=404, detail="Agent not found")
5527
5533
 
5528
5534
  mem = _get_memory()
5529
- # Listed AS this agent, not as the server. ``list`` keeps an entry only if
5530
- # it is shared or the listing identity's own, so asking the server's handle
5531
- # produced a page that could not see the agent's PRIVATE entries at all: an
5532
- # agent whose every entry was private showed 0 memories and 0 topics while
5533
- # its card, counted by SQL with no such filter, correctly said 4 and 1.
5534
- entries = mem.as_agent(agent_id).list()
5535
- if vis is not None and vis.should_filter():
5535
+ # Acting AS this agent, not as the server. ``search`` and ``list`` keep an
5536
+ # entry only if it is shared or the acting identity's own, so asking the
5537
+ # server's handle produced a page that could not see the agent's PRIVATE
5538
+ # entries at all: an agent whose every entry was private showed 0 memories
5539
+ # and 0 topics while its card, counted by SQL with no such filter,
5540
+ # correctly said 4 and 1.
5541
+ handle = mem.as_agent(agent_id)
5542
+ ceiling = max_scan_rows()
5543
+
5544
+ def _scoped(rows: list) -> list:
5536
5545
  # The same per-user filter every other entry-returning route applies,
5537
5546
  # and this one did not: agent ids are matched as bare strings within an
5538
5547
  # account, so where two people share an account and their agents share a
5539
5548
  # default name, each was shown the other's entries.
5540
- entries = vis.filter_entries(entries)
5549
+ if vis is not None and vis.should_filter():
5550
+ return vis.filter_entries(rows)
5551
+ return rows
5552
+
5553
+ # This agent's own entries, filtered by author in SQL. This used to be
5554
+ # ``handle.list()`` — every current entry in the namespace, 400k rows on a
5555
+ # large account, 208 s in production — filtered down to the agent's 69 in
5556
+ # Python afterwards. Fetched one past the ceiling so the flag is exact,
5557
+ # and the flag is read off the RAW rows: the per-user filter below runs
5558
+ # after the limit, so where two users' agents share a name, a page at the
5559
+ # ceiling may hold rows this caller cannot see in place of ones they can.
5560
+ # The response cannot recover those, but it must not call itself complete.
5561
+ own_raw, own_truncated = _bounded_scan(
5562
+ handle.search(agent_id=agent_id, limit=ceiling + 1), ceiling,
5563
+ )
5564
+ own_entries = _scoped(own_raw)
5541
5565
  # Read counts and the trace count are aggregated by the adapter; this
5542
5566
  # used to pull up to 10,000 full traces to tally causal_entries here.
5543
5567
  trace_count = mem._adapter.count_traces(agent_id=agent_id)
@@ -5546,8 +5570,7 @@ def agent_memory_graph(
5546
5570
  }
5547
5571
 
5548
5572
  written_by_agent = [
5549
- e for e in entries
5550
- if e.provenance.agent_id == agent_id and not e.entity_path.startswith("_system/")
5573
+ e for e in own_entries if not e.entity_path.startswith("_system/")
5551
5574
  ]
5552
5575
  entities_written: dict[str, list[dict]] = {}
5553
5576
  for e in written_by_agent:
@@ -5576,7 +5599,6 @@ def agent_memory_graph(
5576
5599
  # type, and the response says when the bound was hit.
5577
5600
  total_read_events = 0
5578
5601
  read_events_truncated = False
5579
- ceiling = max_scan_rows()
5580
5602
  try:
5581
5603
  read_events, cut_a = _bounded_scan(
5582
5604
  mem._adapter.list_events(
@@ -5602,9 +5624,21 @@ def agent_memory_graph(
5602
5624
  except Exception:
5603
5625
  pass
5604
5626
 
5627
+ # Who wrote what this agent read. Only the entity paths it actually read
5628
+ # are listed — a few dozen at most, each an indexed lookup — rather than
5629
+ # the whole namespace. Bounded so an agent that has read everywhere does
5630
+ # not turn this back into the scan it replaces; the flag says when it hit.
5605
5631
  entry_authors: dict[str, str] = {}
5606
- for e in entries:
5632
+ for e in own_entries:
5607
5633
  entry_authors[f"{e.entity_path}/{e.key}"] = e.provenance.agent_id
5634
+ read_paths = sorted(read_entities)
5635
+ authors_truncated = len(read_paths) > _MEMORY_GRAPH_MAX_READ_PATHS
5636
+ for ep in read_paths[:_MEMORY_GRAPH_MAX_READ_PATHS]:
5637
+ try:
5638
+ for e in _scoped(handle.list(ep)):
5639
+ entry_authors.setdefault(f"{ep}/{e.key}", e.provenance.agent_id)
5640
+ except Exception:
5641
+ logger.debug("memory-graph: could not list %s for authors", ep, exc_info=True)
5608
5642
 
5609
5643
  cross_agent_reads: dict[str, list[dict]] = {}
5610
5644
  for ep, keys in read_entities.items():
@@ -5636,7 +5670,7 @@ def agent_memory_graph(
5636
5670
  "totalRecalls": total_recalls,
5637
5671
  "recalledTokensSaved": recalled_tokens_saved,
5638
5672
  "crossAgentReads": cross_agent_reads,
5639
- "truncated": read_events_truncated,
5673
+ "truncated": read_events_truncated or own_truncated or authors_truncated,
5640
5674
  }
5641
5675
 
5642
5676