aethergraph 0.1.0a1__py3-none-any.whl → 0.1.0a3__py3-none-any.whl
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.
- aethergraph/__init__.py +4 -10
- aethergraph/__main__.py +296 -0
- aethergraph/api/v1/__init__.py +0 -0
- aethergraph/api/v1/agents.py +46 -0
- aethergraph/api/v1/apps.py +70 -0
- aethergraph/api/v1/artifacts.py +415 -0
- aethergraph/api/v1/channels.py +89 -0
- aethergraph/api/v1/deps.py +168 -0
- aethergraph/api/v1/graphs.py +259 -0
- aethergraph/api/v1/identity.py +25 -0
- aethergraph/api/v1/memory.py +353 -0
- aethergraph/api/v1/misc.py +47 -0
- aethergraph/api/v1/pagination.py +29 -0
- aethergraph/api/v1/runs.py +568 -0
- aethergraph/api/v1/schemas.py +535 -0
- aethergraph/api/v1/session.py +323 -0
- aethergraph/api/v1/stats.py +201 -0
- aethergraph/api/v1/viz.py +152 -0
- aethergraph/config/config.py +22 -0
- aethergraph/config/loader.py +3 -2
- aethergraph/config/storage.py +209 -0
- aethergraph/contracts/__init__.py +0 -0
- aethergraph/contracts/services/__init__.py +0 -0
- aethergraph/contracts/services/artifacts.py +27 -14
- aethergraph/contracts/services/memory.py +45 -17
- aethergraph/contracts/services/metering.py +129 -0
- aethergraph/contracts/services/runs.py +50 -0
- aethergraph/contracts/services/sessions.py +87 -0
- aethergraph/contracts/services/state_stores.py +3 -0
- aethergraph/contracts/services/viz.py +44 -0
- aethergraph/contracts/storage/artifact_index.py +88 -0
- aethergraph/contracts/storage/artifact_store.py +99 -0
- aethergraph/contracts/storage/async_kv.py +34 -0
- aethergraph/contracts/storage/blob_store.py +50 -0
- aethergraph/contracts/storage/doc_store.py +35 -0
- aethergraph/contracts/storage/event_log.py +31 -0
- aethergraph/contracts/storage/vector_index.py +48 -0
- aethergraph/core/__init__.py +0 -0
- aethergraph/core/execution/forward_scheduler.py +13 -2
- aethergraph/core/execution/global_scheduler.py +21 -15
- aethergraph/core/execution/step_forward.py +10 -1
- aethergraph/core/graph/__init__.py +0 -0
- aethergraph/core/graph/graph_builder.py +8 -4
- aethergraph/core/graph/graph_fn.py +156 -15
- aethergraph/core/graph/graph_spec.py +8 -0
- aethergraph/core/graph/graphify.py +146 -27
- aethergraph/core/graph/node_spec.py +0 -2
- aethergraph/core/graph/node_state.py +3 -0
- aethergraph/core/graph/task_graph.py +39 -1
- aethergraph/core/runtime/__init__.py +0 -0
- aethergraph/core/runtime/ad_hoc_context.py +64 -4
- aethergraph/core/runtime/base_service.py +28 -4
- aethergraph/core/runtime/execution_context.py +13 -15
- aethergraph/core/runtime/graph_runner.py +222 -37
- aethergraph/core/runtime/node_context.py +510 -6
- aethergraph/core/runtime/node_services.py +12 -5
- aethergraph/core/runtime/recovery.py +15 -1
- aethergraph/core/runtime/run_manager.py +783 -0
- aethergraph/core/runtime/run_manager_local.py +204 -0
- aethergraph/core/runtime/run_registration.py +2 -2
- aethergraph/core/runtime/run_types.py +89 -0
- aethergraph/core/runtime/runtime_env.py +136 -7
- aethergraph/core/runtime/runtime_metering.py +71 -0
- aethergraph/core/runtime/runtime_registry.py +36 -13
- aethergraph/core/runtime/runtime_services.py +194 -6
- aethergraph/core/tools/builtins/toolset.py +1 -1
- aethergraph/core/tools/toolkit.py +5 -0
- aethergraph/plugins/agents/default_chat_agent copy.py +90 -0
- aethergraph/plugins/agents/default_chat_agent.py +171 -0
- aethergraph/plugins/agents/shared.py +81 -0
- aethergraph/plugins/channel/adapters/webui.py +112 -112
- aethergraph/plugins/channel/routes/webui_routes.py +367 -102
- aethergraph/plugins/channel/utils/slack_utils.py +115 -59
- aethergraph/plugins/channel/utils/telegram_utils.py +88 -47
- aethergraph/plugins/channel/websockets/weibui_ws.py +172 -0
- aethergraph/runtime/__init__.py +15 -0
- aethergraph/server/app_factory.py +196 -34
- aethergraph/server/clients/channel_client.py +202 -0
- aethergraph/server/http/channel_http_routes.py +116 -0
- aethergraph/server/http/channel_ws_routers.py +45 -0
- aethergraph/server/loading.py +117 -0
- aethergraph/server/server.py +131 -0
- aethergraph/server/server_state.py +240 -0
- aethergraph/server/start.py +227 -66
- aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- aethergraph/server/ui_static/assets/index-BR5GtXcZ.css +1 -0
- aethergraph/server/ui_static/assets/index-CQ0HZZ83.js +400 -0
- aethergraph/server/ui_static/index.html +15 -0
- aethergraph/server/ui_static/logo.png +0 -0
- aethergraph/services/artifacts/__init__.py +0 -0
- aethergraph/services/artifacts/facade.py +1239 -132
- aethergraph/services/auth/{dev.py → authn.py} +0 -8
- aethergraph/services/auth/authz.py +100 -0
- aethergraph/services/channel/__init__.py +0 -0
- aethergraph/services/channel/channel_bus.py +19 -1
- aethergraph/services/channel/factory.py +13 -1
- aethergraph/services/channel/ingress.py +311 -0
- aethergraph/services/channel/queue_adapter.py +75 -0
- aethergraph/services/channel/session.py +502 -19
- aethergraph/services/container/default_container.py +122 -43
- aethergraph/services/continuations/continuation.py +6 -0
- aethergraph/services/continuations/stores/fs_store.py +19 -0
- aethergraph/services/eventhub/event_hub.py +76 -0
- aethergraph/services/kv/__init__.py +0 -0
- aethergraph/services/kv/ephemeral.py +244 -0
- aethergraph/services/llm/__init__.py +0 -0
- aethergraph/services/llm/generic_client copy.py +691 -0
- aethergraph/services/llm/generic_client.py +1288 -187
- aethergraph/services/llm/providers.py +3 -1
- aethergraph/services/llm/types.py +47 -0
- aethergraph/services/llm/utils.py +284 -0
- aethergraph/services/logger/std.py +3 -0
- aethergraph/services/mcp/__init__.py +9 -0
- aethergraph/services/mcp/http_client.py +38 -0
- aethergraph/services/mcp/service.py +225 -1
- aethergraph/services/mcp/stdio_client.py +41 -6
- aethergraph/services/mcp/ws_client.py +44 -2
- aethergraph/services/memory/__init__.py +0 -0
- aethergraph/services/memory/distillers/llm_long_term.py +234 -0
- aethergraph/services/memory/distillers/llm_meta_summary.py +398 -0
- aethergraph/services/memory/distillers/long_term.py +225 -0
- aethergraph/services/memory/facade/__init__.py +3 -0
- aethergraph/services/memory/facade/chat.py +440 -0
- aethergraph/services/memory/facade/core.py +447 -0
- aethergraph/services/memory/facade/distillation.py +424 -0
- aethergraph/services/memory/facade/rag.py +410 -0
- aethergraph/services/memory/facade/results.py +315 -0
- aethergraph/services/memory/facade/retrieval.py +139 -0
- aethergraph/services/memory/facade/types.py +77 -0
- aethergraph/services/memory/facade/utils.py +43 -0
- aethergraph/services/memory/facade_dep.py +1539 -0
- aethergraph/services/memory/factory.py +9 -3
- aethergraph/services/memory/utils.py +10 -0
- aethergraph/services/metering/eventlog_metering.py +470 -0
- aethergraph/services/metering/noop.py +25 -4
- aethergraph/services/rag/__init__.py +0 -0
- aethergraph/services/rag/facade.py +279 -23
- aethergraph/services/rag/index_factory.py +2 -2
- aethergraph/services/rag/node_rag.py +317 -0
- aethergraph/services/rate_limit/inmem_rate_limit.py +24 -0
- aethergraph/services/registry/__init__.py +0 -0
- aethergraph/services/registry/agent_app_meta.py +419 -0
- aethergraph/services/registry/registry_key.py +1 -1
- aethergraph/services/registry/unified_registry.py +74 -6
- aethergraph/services/scope/scope.py +159 -0
- aethergraph/services/scope/scope_factory.py +164 -0
- aethergraph/services/state_stores/serialize.py +5 -0
- aethergraph/services/state_stores/utils.py +2 -1
- aethergraph/services/viz/__init__.py +0 -0
- aethergraph/services/viz/facade.py +413 -0
- aethergraph/services/viz/viz_service.py +69 -0
- aethergraph/storage/artifacts/artifact_index_jsonl.py +180 -0
- aethergraph/storage/artifacts/artifact_index_sqlite.py +426 -0
- aethergraph/storage/artifacts/cas_store.py +422 -0
- aethergraph/storage/artifacts/fs_cas.py +18 -0
- aethergraph/storage/artifacts/s3_cas.py +14 -0
- aethergraph/storage/artifacts/utils.py +124 -0
- aethergraph/storage/blob/fs_blob.py +86 -0
- aethergraph/storage/blob/s3_blob.py +115 -0
- aethergraph/storage/continuation_store/fs_cont.py +283 -0
- aethergraph/storage/continuation_store/inmem_cont.py +146 -0
- aethergraph/storage/continuation_store/kvdoc_cont.py +261 -0
- aethergraph/storage/docstore/fs_doc.py +63 -0
- aethergraph/storage/docstore/sqlite_doc.py +31 -0
- aethergraph/storage/docstore/sqlite_doc_sync.py +90 -0
- aethergraph/storage/eventlog/fs_event.py +136 -0
- aethergraph/storage/eventlog/sqlite_event.py +47 -0
- aethergraph/storage/eventlog/sqlite_event_sync.py +178 -0
- aethergraph/storage/factory.py +432 -0
- aethergraph/storage/fs_utils.py +28 -0
- aethergraph/storage/graph_state_store/state_store.py +64 -0
- aethergraph/storage/kv/inmem_kv.py +103 -0
- aethergraph/storage/kv/layered_kv.py +52 -0
- aethergraph/storage/kv/sqlite_kv.py +39 -0
- aethergraph/storage/kv/sqlite_kv_sync.py +98 -0
- aethergraph/storage/memory/event_persist.py +68 -0
- aethergraph/storage/memory/fs_persist.py +118 -0
- aethergraph/{services/memory/hotlog_kv.py → storage/memory/hotlog.py} +8 -2
- aethergraph/{services → storage}/memory/indices.py +31 -7
- aethergraph/storage/metering/meter_event.py +55 -0
- aethergraph/storage/runs/doc_store.py +280 -0
- aethergraph/storage/runs/inmen_store.py +82 -0
- aethergraph/storage/runs/sqlite_run_store.py +403 -0
- aethergraph/storage/sessions/doc_store.py +183 -0
- aethergraph/storage/sessions/inmem_store.py +110 -0
- aethergraph/storage/sessions/sqlite_session_store.py +399 -0
- aethergraph/storage/vector_index/chroma_index.py +138 -0
- aethergraph/storage/vector_index/faiss_index.py +179 -0
- aethergraph/storage/vector_index/sqlite_index.py +187 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/METADATA +138 -31
- aethergraph-0.1.0a3.dist-info/RECORD +356 -0
- aethergraph-0.1.0a3.dist-info/entry_points.txt +3 -0
- aethergraph/services/artifacts/factory.py +0 -35
- aethergraph/services/artifacts/fs_store.py +0 -656
- aethergraph/services/artifacts/jsonl_index.py +0 -123
- aethergraph/services/artifacts/sqlite_index.py +0 -209
- aethergraph/services/memory/distillers/episode.py +0 -116
- aethergraph/services/memory/distillers/rolling.py +0 -74
- aethergraph/services/memory/facade.py +0 -633
- aethergraph/services/memory/persist_fs.py +0 -40
- aethergraph/services/rag/index/base.py +0 -27
- aethergraph/services/rag/index/faiss_index.py +0 -121
- aethergraph/services/rag/index/sqlite_index.py +0 -134
- aethergraph-0.1.0a1.dist-info/RECORD +0 -182
- aethergraph-0.1.0a1.dist-info/entry_points.txt +0 -2
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/WHEEL +0 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/licenses/LICENSE +0 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/licenses/NOTICE +0 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/top_level.txt +0 -0
|
@@ -5,26 +5,45 @@ from pathlib import Path
|
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
7
|
from aethergraph.contracts.services.llm import LLMClientProtocol
|
|
8
|
+
from aethergraph.core.runtime.base_service import Service
|
|
8
9
|
from aethergraph.services.llm.generic_client import GenericLLMClient
|
|
9
10
|
|
|
10
11
|
_current = ContextVar("aeg_services", default=None)
|
|
11
12
|
# process-wide fallback (handles contextvar boundary issues)
|
|
12
13
|
_services_global: Any = None
|
|
14
|
+
# allow registering external services before main services are ready
|
|
15
|
+
_pending_ext_services: dict[str, Any] = {}
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
def install_services(services: Any) -> None:
|
|
16
|
-
global _services_global
|
|
19
|
+
global _services_global, _pending_ext_services
|
|
17
20
|
_services_global = services
|
|
21
|
+
|
|
22
|
+
# Attach any services that were registered before install_services().
|
|
23
|
+
ext = getattr(services, "ext_services", None)
|
|
24
|
+
if isinstance(ext, dict) and _pending_ext_services:
|
|
25
|
+
# Don't clobber anything that was already present.
|
|
26
|
+
for name, svc in _pending_ext_services.items():
|
|
27
|
+
ext.setdefault(name, svc)
|
|
28
|
+
_pending_ext_services = {}
|
|
29
|
+
|
|
18
30
|
return _current.set(services)
|
|
19
31
|
|
|
20
32
|
|
|
21
33
|
def ensure_services_installed(factory: Callable[[], Any]) -> Any:
|
|
22
|
-
global _services_global
|
|
34
|
+
global _services_global, _pending_ext_services
|
|
23
35
|
svc = _current.get() or _services_global
|
|
24
36
|
if svc is None:
|
|
25
37
|
svc = factory()
|
|
26
38
|
_services_global = svc
|
|
27
|
-
|
|
39
|
+
|
|
40
|
+
# hydrate pending external services here too
|
|
41
|
+
ext = getattr(svc, "ext_services", None)
|
|
42
|
+
if isinstance(ext, dict) and _pending_ext_services:
|
|
43
|
+
for name, s in _pending_ext_services.items():
|
|
44
|
+
ext.setdefault(name, s)
|
|
45
|
+
_pending_ext_services = {}
|
|
46
|
+
_current.set(svc)
|
|
28
47
|
return svc
|
|
29
48
|
|
|
30
49
|
|
|
@@ -174,33 +193,182 @@ def current_logger_factory() -> Any:
|
|
|
174
193
|
|
|
175
194
|
|
|
176
195
|
# --------- External context services ---------
|
|
177
|
-
def register_context_service(name: str, service:
|
|
178
|
-
|
|
196
|
+
def register_context_service(name: str, service: Service) -> None:
|
|
197
|
+
"""
|
|
198
|
+
Register an external service for NodeContext access.
|
|
199
|
+
|
|
200
|
+
This function attaches an external service to the current service container
|
|
201
|
+
under the specified name. If no container is installed yet, the service is
|
|
202
|
+
stashed in a pending registry and will be attached automatically when
|
|
203
|
+
install_services() is called.
|
|
204
|
+
|
|
205
|
+
Examples:
|
|
206
|
+
Register a custom database service:
|
|
207
|
+
```python
|
|
208
|
+
register_context_service("mydb", MyDatabaseService())
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
name: The unique string identifier for the external service.
|
|
213
|
+
service: The service instance to register.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
None
|
|
217
|
+
|
|
218
|
+
Notes:
|
|
219
|
+
- If called before install_services(), the service will be attached later.
|
|
220
|
+
- Services are accessible via NodeContext.ext_services[name].
|
|
221
|
+
"""
|
|
222
|
+
global _pending_ext_services
|
|
223
|
+
|
|
224
|
+
try:
|
|
225
|
+
svc = current_services()
|
|
226
|
+
except RuntimeError:
|
|
227
|
+
# No container yet: keep it in the staging area.
|
|
228
|
+
_pending_ext_services[name] = service
|
|
229
|
+
return
|
|
230
|
+
|
|
231
|
+
# Container exists: attach immediately.
|
|
179
232
|
svc.ext_services[name] = service
|
|
180
233
|
|
|
181
234
|
|
|
182
|
-
def get_ext_context_service(name: str) ->
|
|
235
|
+
def get_ext_context_service(name: str) -> Service:
|
|
236
|
+
"""
|
|
237
|
+
Retrieve an external context service by name.
|
|
238
|
+
|
|
239
|
+
This function returns the external service registered under the given name
|
|
240
|
+
from the current service container's ext_services registry.
|
|
241
|
+
|
|
242
|
+
Examples:
|
|
243
|
+
Access a registered service:
|
|
244
|
+
```python
|
|
245
|
+
mydb = get_ext_context_service("mydb")
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
name: The string name of the external service to retrieve.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
The service instance registered under the given name, or None if not found.
|
|
253
|
+
|
|
254
|
+
Raises:
|
|
255
|
+
RuntimeError: If no services container is installed.
|
|
256
|
+
"""
|
|
183
257
|
svc = current_services()
|
|
184
258
|
return svc.ext_services.get(name)
|
|
185
259
|
|
|
186
260
|
|
|
187
261
|
def list_ext_context_services() -> list[str]:
|
|
262
|
+
"""
|
|
263
|
+
List all registered external context service names.
|
|
264
|
+
|
|
265
|
+
This function returns a list of all names for services currently registered
|
|
266
|
+
in the ext_services registry of the current service container.
|
|
267
|
+
|
|
268
|
+
Examples:
|
|
269
|
+
List all available external services:
|
|
270
|
+
```python
|
|
271
|
+
services = list_ext_context_services()
|
|
272
|
+
print(services)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
None
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
A list of strings representing the names of all registered external services.
|
|
280
|
+
Returns an empty list if no services are registered.
|
|
281
|
+
|
|
282
|
+
Raises:
|
|
283
|
+
RuntimeError: If no services container is installed.
|
|
284
|
+
"""
|
|
188
285
|
svc = current_services()
|
|
189
286
|
return list(svc.ext_services.keys())
|
|
190
287
|
|
|
191
288
|
|
|
192
289
|
# --------- MCP service helpers ---------
|
|
193
290
|
def set_mcp_service(mcp_service: Any) -> None:
|
|
291
|
+
"""
|
|
292
|
+
Set the MCP service in the current service container.
|
|
293
|
+
|
|
294
|
+
This function assigns the provided MCP service instance to the current application's
|
|
295
|
+
service container, making it available for subsequent MCP client registrations and lookups.
|
|
296
|
+
|
|
297
|
+
Examples:
|
|
298
|
+
```python
|
|
299
|
+
from aethergraph.runtime import set_mcp_service
|
|
300
|
+
set_mcp_service(MyMCPService())
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
mcp_service: An instance implementing the MCP service interface.
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
None
|
|
308
|
+
|
|
309
|
+
Notes:
|
|
310
|
+
- This should be called once during application startup before registering MCP clients.
|
|
311
|
+
- This is an internal function; users typically interact with MCP services via higher-level APIs.
|
|
312
|
+
"""
|
|
194
313
|
svc = current_services()
|
|
195
314
|
svc.mcp = mcp_service
|
|
196
315
|
|
|
197
316
|
|
|
198
317
|
def get_mcp_service() -> Any:
|
|
318
|
+
"""
|
|
319
|
+
Retrieve the currently configured MCP service.
|
|
320
|
+
|
|
321
|
+
This function returns the MCP service instance from the current application's
|
|
322
|
+
service container. It is used to access MCP-related functionality throughout the app.
|
|
323
|
+
|
|
324
|
+
Examples:
|
|
325
|
+
```python
|
|
326
|
+
mcp = get_mcp_service()
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
None
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
The MCP service instance currently set in the service container.
|
|
334
|
+
|
|
335
|
+
Raises:
|
|
336
|
+
RuntimeError: If no MCP service has been set.
|
|
337
|
+
|
|
338
|
+
Notes:
|
|
339
|
+
- Ensure that set_mcp_service() has been called during application initialization.
|
|
340
|
+
- This is an internal function; users typically interact with MCP services via higher-level APIs.
|
|
341
|
+
"""
|
|
199
342
|
svc = current_services()
|
|
200
343
|
return svc.mcp
|
|
201
344
|
|
|
202
345
|
|
|
203
346
|
def register_mcp_client(name: str, client: Any) -> None:
|
|
347
|
+
"""
|
|
348
|
+
Register a new MCP client with the current MCP service.
|
|
349
|
+
|
|
350
|
+
This function adds a client instance to the MCP service under the specified name,
|
|
351
|
+
allowing it to be accessed and managed by the MCP infrastructure.
|
|
352
|
+
|
|
353
|
+
Examples:
|
|
354
|
+
```python
|
|
355
|
+
from aethergraph.runtime import register_mcp_client
|
|
356
|
+
from aethergraph.services.mcp import HttpMCPClient
|
|
357
|
+
my_client = HttpMCPClient("https://mcp.example.com", ...)
|
|
358
|
+
register_mcp_client("myclient", my_client)
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
name: The unique name to associate with the MCP client.
|
|
363
|
+
client: The client instance to register.
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
None
|
|
367
|
+
|
|
368
|
+
Raises:
|
|
369
|
+
RuntimeError: If no MCP service has been installed via set_mcp_service().
|
|
370
|
+
|
|
371
|
+
"""
|
|
204
372
|
svc = current_services()
|
|
205
373
|
if svc.mcp is None:
|
|
206
374
|
raise RuntimeError("No MCP service installed. Call set_mcp_service() first.")
|
|
@@ -208,6 +376,26 @@ def register_mcp_client(name: str, client: Any) -> None:
|
|
|
208
376
|
|
|
209
377
|
|
|
210
378
|
def list_mcp_clients() -> list[str]:
|
|
379
|
+
"""
|
|
380
|
+
List all registered MCP client names in the current MCP service.
|
|
381
|
+
|
|
382
|
+
This function returns a list of all client names that have been registered
|
|
383
|
+
with the MCP service, allowing for discovery and management of available clients.
|
|
384
|
+
|
|
385
|
+
Examples:
|
|
386
|
+
```python
|
|
387
|
+
from aethergraph.runtime import list_mcp_clients
|
|
388
|
+
clients = list_mcp_clients()
|
|
389
|
+
print(clients)
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
None
|
|
394
|
+
|
|
395
|
+
Returns:
|
|
396
|
+
A list of strings representing the names of registered MCP clients.
|
|
397
|
+
Returns an empty list if no MCP service is installed or no clients are registered.
|
|
398
|
+
"""
|
|
211
399
|
svc = current_services()
|
|
212
400
|
if svc.mcp:
|
|
213
401
|
return svc.mcp.list_clients()
|
|
@@ -77,7 +77,7 @@ async def ask_files(
|
|
|
77
77
|
|
|
78
78
|
@tool(name="send_text", outputs=["ok"])
|
|
79
79
|
async def send_text(
|
|
80
|
-
|
|
80
|
+
text: str, *, meta: dict[str, Any] | None = None, channel: str | None = None, context=None
|
|
81
81
|
):
|
|
82
82
|
ch = context.channel(channel)
|
|
83
83
|
await ch.send_text(text, meta=meta or {})
|
|
@@ -141,11 +141,16 @@ def tool(
|
|
|
141
141
|
# registry behavior
|
|
142
142
|
registry = current_registry()
|
|
143
143
|
if registry is not None:
|
|
144
|
+
meta = {
|
|
145
|
+
"kind": "tool",
|
|
146
|
+
"tags": [],
|
|
147
|
+
}
|
|
144
148
|
registry.register(
|
|
145
149
|
nspace="tool",
|
|
146
150
|
name=name or getattr(impl, "__name__", "tool"),
|
|
147
151
|
version=version,
|
|
148
152
|
obj=impl,
|
|
153
|
+
meta=meta,
|
|
149
154
|
)
|
|
150
155
|
|
|
151
156
|
return proxy
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# aethergraph/examples/agents/default_chat_agent.py (or similar)
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from aethergraph import NodeContext, graph_fn
|
|
8
|
+
from aethergraph.plugins.agents.shared import build_session_memory_prompt_segments
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@graph_fn(
|
|
12
|
+
name="default_chat_agent",
|
|
13
|
+
inputs=["message", "files", "session_id", "user_meta"],
|
|
14
|
+
outputs=["reply"],
|
|
15
|
+
as_agent={
|
|
16
|
+
"id": "chat_agent",
|
|
17
|
+
"title": "Chat",
|
|
18
|
+
"description": "Built-in chat agent that uses the configured LLM.",
|
|
19
|
+
"icon": "message-circle",
|
|
20
|
+
"color": "sky",
|
|
21
|
+
"session_kind": "chat",
|
|
22
|
+
"mode": "chat_v1",
|
|
23
|
+
"memory_level": "session",
|
|
24
|
+
"memory_scope": "session.global",
|
|
25
|
+
},
|
|
26
|
+
)
|
|
27
|
+
async def default_chat_agent(
|
|
28
|
+
message: str,
|
|
29
|
+
files: list[Any] | None = None,
|
|
30
|
+
session_id: str | None = None,
|
|
31
|
+
user_meta: dict[str, Any] | None = None,
|
|
32
|
+
context_refs: list[dict[str, Any]] | None = None,
|
|
33
|
+
*,
|
|
34
|
+
context: NodeContext,
|
|
35
|
+
):
|
|
36
|
+
"""
|
|
37
|
+
Simple built-in chat agent:
|
|
38
|
+
|
|
39
|
+
- Takes {message, files}
|
|
40
|
+
- Calls the configured LLM
|
|
41
|
+
- Uses shared session memory (summary + recent events) in the prompt.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
llm = context.llm()
|
|
45
|
+
chan = context.ui_session_channel()
|
|
46
|
+
|
|
47
|
+
# 1) Build memory segments for this session
|
|
48
|
+
session_summary, recent_events = await build_session_memory_prompt_segments(
|
|
49
|
+
context,
|
|
50
|
+
summary_tag="session",
|
|
51
|
+
recent_limit=12,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# print("Session summary:", session_summary)
|
|
55
|
+
# print("Recent events:", recent_events)
|
|
56
|
+
|
|
57
|
+
# 2) System + user messages (you can move this into PromptStore later)
|
|
58
|
+
system_prompt = (
|
|
59
|
+
"You are AetherGraph's built-in session helper.\n\n"
|
|
60
|
+
"You can see a short summary of the session and a few recent events from all agents.\n"
|
|
61
|
+
"Use them to answer questions about previous steps or runs, but do not invent details.\n"
|
|
62
|
+
"If you are unsure, say that clearly.\n"
|
|
63
|
+
"When return math or code snippets, use markdown formatting.\n"
|
|
64
|
+
"Math formatting rules:\n"
|
|
65
|
+
"- Use LaTeX math delimiters:\n"
|
|
66
|
+
" - Inline: \\( ... \\) (no extra spaces right after \\( or before \\))\n"
|
|
67
|
+
" - Display: $$ ... $$ (for standalone equations)\n"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
memory_context = ""
|
|
71
|
+
if session_summary:
|
|
72
|
+
memory_context += f"Session summary:\n{session_summary}\n\n"
|
|
73
|
+
if recent_events:
|
|
74
|
+
memory_context += f"Recent events:\n{recent_events}\n\n"
|
|
75
|
+
|
|
76
|
+
user_prompt = f"{memory_context}" "User message:\n" f"{message}\n"
|
|
77
|
+
|
|
78
|
+
# 3) Call LLM with chat-style API
|
|
79
|
+
resp, _usage = await llm.chat(
|
|
80
|
+
messages=[
|
|
81
|
+
{"role": "system", "content": system_prompt},
|
|
82
|
+
{"role": "user", "content": user_prompt},
|
|
83
|
+
],
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
await chan.send_text(resp)
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
"reply": resp,
|
|
90
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# aethergraph/examples/agents/default_chat_agent.py (or similar)
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from aethergraph import NodeContext, graph_fn
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@graph_fn(
|
|
11
|
+
name="default_chat_agent",
|
|
12
|
+
inputs=["message", "files", "session_id", "user_meta"],
|
|
13
|
+
outputs=["reply"],
|
|
14
|
+
as_agent={
|
|
15
|
+
"id": "chat_agent",
|
|
16
|
+
"title": "Chat",
|
|
17
|
+
"short_description": "General-purpose chat agent.",
|
|
18
|
+
"description": "Built-in chat agent that uses the configured LLM and memory across sessions.",
|
|
19
|
+
"icon": "message-circle",
|
|
20
|
+
"color": "sky",
|
|
21
|
+
"session_kind": "chat",
|
|
22
|
+
"mode": "chat_v1",
|
|
23
|
+
"memory_level": "session",
|
|
24
|
+
"memory_scope": "session.global",
|
|
25
|
+
},
|
|
26
|
+
)
|
|
27
|
+
async def default_chat_agent(
|
|
28
|
+
message: str,
|
|
29
|
+
files: list[Any] | None = None,
|
|
30
|
+
session_id: str | None = None,
|
|
31
|
+
user_meta: dict[str, Any] | None = None,
|
|
32
|
+
context_refs: list[dict[str, Any]] | None = None,
|
|
33
|
+
*,
|
|
34
|
+
context: NodeContext,
|
|
35
|
+
):
|
|
36
|
+
"""
|
|
37
|
+
Built-in chat agent with session memory:
|
|
38
|
+
|
|
39
|
+
- Hydrates long-term + recent chat memory into the prompt.
|
|
40
|
+
- Records user and assistant messages as chat.turn events.
|
|
41
|
+
- Periodically distills chat history into long-term summaries.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
llm = context.llm()
|
|
45
|
+
chan = context.ui_session_channel()
|
|
46
|
+
|
|
47
|
+
mem = context.memory()
|
|
48
|
+
|
|
49
|
+
# 1) Build memory segments for this session
|
|
50
|
+
long_term_summary: str = ""
|
|
51
|
+
recent_chat: list[dict[str, Any]] = []
|
|
52
|
+
|
|
53
|
+
"""
|
|
54
|
+
Build prompt segments:
|
|
55
|
+
{
|
|
56
|
+
"long_term": "<combined summary text or ''>",
|
|
57
|
+
"recent_chat": [ {ts, role, text, tags}, ... ],
|
|
58
|
+
"recent_tools": [ {ts, tool, message, inputs, outputs, tags}, ... ]
|
|
59
|
+
}
|
|
60
|
+
"""
|
|
61
|
+
segments = await mem.build_prompt_segments(
|
|
62
|
+
recent_chat_limit=20,
|
|
63
|
+
include_long_term=True,
|
|
64
|
+
summary_tag="session",
|
|
65
|
+
max_summaries=3,
|
|
66
|
+
include_recent_tools=False,
|
|
67
|
+
)
|
|
68
|
+
long_term_summary = segments.get("long_term") or ""
|
|
69
|
+
recent_chat = segments.get("recent_chat") or []
|
|
70
|
+
|
|
71
|
+
# 2) System prompt
|
|
72
|
+
system_prompt = (
|
|
73
|
+
"You are AetherGraph's built-in session helper.\n\n"
|
|
74
|
+
"You can see a summary of the session and some recent messages.\n"
|
|
75
|
+
"Use them to answer questions about previous steps or runs, but do not invent details.\n"
|
|
76
|
+
"If you are unsure, say that clearly.\n"
|
|
77
|
+
# "When returning math or code snippets, use markdown formatting.\n"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
messages: list[dict[str, str]] = [
|
|
81
|
+
{"role": "system", "content": system_prompt},
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
# Inject long-term summary as a system message (if present)
|
|
85
|
+
if long_term_summary:
|
|
86
|
+
messages.append(
|
|
87
|
+
{
|
|
88
|
+
"role": "system",
|
|
89
|
+
"content": "Summary of previous context:\n" + long_term_summary,
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Inject recent chat as prior turns
|
|
94
|
+
for item in recent_chat:
|
|
95
|
+
role = item.get("role") or "user"
|
|
96
|
+
text = item.get("text") or ""
|
|
97
|
+
# Map non-standard roles (e.g. "tool") to "assistant" for chat APIs
|
|
98
|
+
mapped_role = role if role in {"user", "assistant", "system"} else "assistant"
|
|
99
|
+
if text:
|
|
100
|
+
messages.append({"role": mapped_role, "content": text})
|
|
101
|
+
|
|
102
|
+
# Add some lightweight metadata about files / context refs into the user message
|
|
103
|
+
meta_lines: list[str] = []
|
|
104
|
+
if files:
|
|
105
|
+
meta_lines.append(f"(User attached {len(files)} file(s).)")
|
|
106
|
+
if context_refs:
|
|
107
|
+
meta_lines.append(f"(User attached {len(context_refs)} context reference(s).)")
|
|
108
|
+
meta_block = ""
|
|
109
|
+
if meta_lines:
|
|
110
|
+
meta_block = "\n\n" + "\n".join(meta_lines)
|
|
111
|
+
|
|
112
|
+
user_content = f"{message}{meta_block}"
|
|
113
|
+
|
|
114
|
+
# 3) Record the user message into memory
|
|
115
|
+
user_data: dict[str, Any] = {}
|
|
116
|
+
if files:
|
|
117
|
+
# Store only lightweight file metadata; avoid huge payloads
|
|
118
|
+
user_data["files"] = [
|
|
119
|
+
{k: v for k, v in (f or {}).items() if k in {"name", "url", "mimetype", "size"}}
|
|
120
|
+
for f in files
|
|
121
|
+
]
|
|
122
|
+
if context_refs:
|
|
123
|
+
user_data["context_refs"] = context_refs
|
|
124
|
+
|
|
125
|
+
await mem.record_chat_user(
|
|
126
|
+
message,
|
|
127
|
+
data=user_data,
|
|
128
|
+
tags=["session.chat"],
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Append current user message to LLM prompt
|
|
132
|
+
messages.append({"role": "user", "content": user_content})
|
|
133
|
+
# 4) Call LLM with chat-style API
|
|
134
|
+
resp, _usage = await llm.chat(
|
|
135
|
+
messages=messages,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# 5) Record assistant reply into memory and run simple distillation policy
|
|
139
|
+
try:
|
|
140
|
+
await mem.record_chat_assistant(
|
|
141
|
+
resp,
|
|
142
|
+
tags=["session.chat"],
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Simple distillation policy:
|
|
146
|
+
# If we have "enough" chat turns in recent history, run a long-term summary.
|
|
147
|
+
recent_for_distill = await mem.recent_chat(limit=120)
|
|
148
|
+
if len(recent_for_distill) >= 80:
|
|
149
|
+
# Non-LLM summarizer by default; flip use_llm=True later.
|
|
150
|
+
await mem.distill_long_term(
|
|
151
|
+
summary_tag="session",
|
|
152
|
+
summary_kind="long_term_summary",
|
|
153
|
+
include_kinds=["chat.turn"],
|
|
154
|
+
include_tags=["chat"],
|
|
155
|
+
max_events=200,
|
|
156
|
+
use_llm=False,
|
|
157
|
+
)
|
|
158
|
+
except Exception:
|
|
159
|
+
# Memory issues should never break the chat agent
|
|
160
|
+
import traceback
|
|
161
|
+
|
|
162
|
+
trace = traceback.format_exc()
|
|
163
|
+
logger = context.logger()
|
|
164
|
+
logger.warning("Chat agent memory record/distill error:\n" + trace)
|
|
165
|
+
|
|
166
|
+
# 6) Send reply to UI channel
|
|
167
|
+
await chan.send_text(resp)
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
"reply": resp,
|
|
171
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from aethergraph import NodeContext
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
async def build_session_memory_prompt_segments(
|
|
7
|
+
context: NodeContext,
|
|
8
|
+
*,
|
|
9
|
+
summary_tag: str = "session",
|
|
10
|
+
recent_limit: int = 12,
|
|
11
|
+
) -> tuple[str, str]:
|
|
12
|
+
"""
|
|
13
|
+
Build reusable 'memory segments' for LLM prompts:
|
|
14
|
+
|
|
15
|
+
- session_summary: long-term summary text (may be empty)
|
|
16
|
+
- recent_events: short list of recent events (chat, status, etc.)
|
|
17
|
+
|
|
18
|
+
Any agent that wants cross-agent memory can call this, then
|
|
19
|
+
feed these into its LLM prompt or structured prompt store.
|
|
20
|
+
|
|
21
|
+
The underlying MemoryFacade is responsible for:
|
|
22
|
+
- Choosing a scope_id (usually tied to the session).
|
|
23
|
+
- Storing summaries in DocStore (mem/{scope_id}/summaries/{tag}/...).
|
|
24
|
+
- Exposing hotlog events via mem.recent().
|
|
25
|
+
"""
|
|
26
|
+
mem = None
|
|
27
|
+
try:
|
|
28
|
+
mem = context.memory()
|
|
29
|
+
except TypeError:
|
|
30
|
+
# Depending on how NodeContext is wired, .memory might be property or method
|
|
31
|
+
mem = getattr(context, "memory", None)
|
|
32
|
+
|
|
33
|
+
if mem is None:
|
|
34
|
+
return "", ""
|
|
35
|
+
|
|
36
|
+
# ---- 1) Long-term session summary (if summarization has been run) ----
|
|
37
|
+
session_summary = ""
|
|
38
|
+
try:
|
|
39
|
+
summary = await mem.soft_hydrate_last_summary(
|
|
40
|
+
summary_tag=summary_tag,
|
|
41
|
+
summary_kind="long_term_summary",
|
|
42
|
+
)
|
|
43
|
+
if summary:
|
|
44
|
+
session_summary = summary.get("text") or ""
|
|
45
|
+
except Exception as e:
|
|
46
|
+
logger = getattr(context, "logger", None)
|
|
47
|
+
if logger:
|
|
48
|
+
logger.warning(
|
|
49
|
+
"build_session_memory_prompt_segments: soft_hydrate_last_summary failed",
|
|
50
|
+
extra={"error": str(e)},
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# ---- 2) Recent events across runs/agents in this session ----
|
|
54
|
+
recent_events = ""
|
|
55
|
+
try:
|
|
56
|
+
events = await mem.recent(kinds=None, limit=recent_limit)
|
|
57
|
+
lines: list[str] = []
|
|
58
|
+
for evt in events:
|
|
59
|
+
kind = getattr(evt, "kind", None) or "event"
|
|
60
|
+
text = getattr(evt, "text", None)
|
|
61
|
+
data = getattr(evt, "data", None)
|
|
62
|
+
|
|
63
|
+
# Try to recover some text from data if text is empty
|
|
64
|
+
if not text and isinstance(data, dict):
|
|
65
|
+
text = data.get("text") or data.get("message") or data.get("summary")
|
|
66
|
+
|
|
67
|
+
if not text:
|
|
68
|
+
continue
|
|
69
|
+
|
|
70
|
+
# Keep it short-ish; you can truncate here if needed
|
|
71
|
+
lines.append(f"[{kind}] {text}")
|
|
72
|
+
recent_events = "\n".join(lines)
|
|
73
|
+
except Exception as e:
|
|
74
|
+
logger = getattr(context, "logger", None)
|
|
75
|
+
if logger:
|
|
76
|
+
logger.warning(
|
|
77
|
+
"build_session_memory_prompt_segments: mem.recent failed",
|
|
78
|
+
extra={"error": str(e)},
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return session_summary, recent_events
|