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
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from aethergraph.contracts.services.llm import LLMClientProtocol
|
|
8
|
+
from aethergraph.contracts.services.memory import Event, HotLog, Indices, Persistence
|
|
9
|
+
from aethergraph.contracts.storage.artifact_store import AsyncArtifactStore
|
|
10
|
+
from aethergraph.contracts.storage.doc_store import DocStore
|
|
11
|
+
from aethergraph.core.runtime.runtime_metering import current_metering
|
|
12
|
+
from aethergraph.services.rag.facade import RAGFacade
|
|
13
|
+
from aethergraph.services.scope.scope import Scope
|
|
14
|
+
|
|
15
|
+
from .chat import ChatMixin
|
|
16
|
+
from .distillation import DistillationMixin
|
|
17
|
+
from .rag import RAGMixin
|
|
18
|
+
from .results import ResultMixin
|
|
19
|
+
from .retrieval import RetrievalMixin
|
|
20
|
+
from .utils import now_iso, stable_event_id
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class MemoryFacade(ChatMixin, ResultMixin, RetrievalMixin, DistillationMixin, RAGMixin):
|
|
24
|
+
"""
|
|
25
|
+
MemoryFacade coordinates core memory services for a specific run/session.
|
|
26
|
+
Functionality is split across mixins in the `facade/` directory.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
*,
|
|
32
|
+
run_id: str,
|
|
33
|
+
session_id: str | None,
|
|
34
|
+
graph_id: str | None,
|
|
35
|
+
node_id: str | None,
|
|
36
|
+
scope: Scope | None = None,
|
|
37
|
+
hotlog: HotLog,
|
|
38
|
+
persistence: Persistence,
|
|
39
|
+
indices: Indices,
|
|
40
|
+
docs: DocStore,
|
|
41
|
+
artifact_store: AsyncArtifactStore,
|
|
42
|
+
hot_limit: int = 1000,
|
|
43
|
+
hot_ttl_s: int = 7 * 24 * 3600,
|
|
44
|
+
default_signal_threshold: float = 0.0,
|
|
45
|
+
logger=None,
|
|
46
|
+
rag: RAGFacade | None = None,
|
|
47
|
+
llm: LLMClientProtocol | None = None,
|
|
48
|
+
):
|
|
49
|
+
self.run_id = run_id
|
|
50
|
+
self.session_id = session_id
|
|
51
|
+
self.graph_id = graph_id
|
|
52
|
+
self.node_id = node_id
|
|
53
|
+
self.scope = scope
|
|
54
|
+
self.hotlog = hotlog
|
|
55
|
+
self.persistence = persistence
|
|
56
|
+
self.indices = indices
|
|
57
|
+
self.docs = docs
|
|
58
|
+
self.artifacts = artifact_store
|
|
59
|
+
self.hot_limit = hot_limit
|
|
60
|
+
self.hot_ttl_s = hot_ttl_s
|
|
61
|
+
self.default_signal_threshold = default_signal_threshold
|
|
62
|
+
self.logger = logger or logging.getLogger(__name__)
|
|
63
|
+
self.rag = rag
|
|
64
|
+
self.llm = llm
|
|
65
|
+
|
|
66
|
+
self.memory_scope_id = (
|
|
67
|
+
self.scope.memory_scope_id() if self.scope else self.session_id or self.run_id
|
|
68
|
+
)
|
|
69
|
+
self.timeline_id = self.memory_scope_id or self.run_id
|
|
70
|
+
|
|
71
|
+
async def record_raw(
|
|
72
|
+
self,
|
|
73
|
+
*,
|
|
74
|
+
base: dict[str, Any],
|
|
75
|
+
text: str | None = None,
|
|
76
|
+
metrics: dict[str, float] | None = None,
|
|
77
|
+
) -> Event:
|
|
78
|
+
"""
|
|
79
|
+
Record an unstructured event with optional preview text and metrics.
|
|
80
|
+
|
|
81
|
+
This method generates a stable event ID, populates standard fields
|
|
82
|
+
(e.g., `run_id`, `scope_id`, `severity`, `signal`), and appends the
|
|
83
|
+
event to both the HotLog and Persistence layers. Additionally, it
|
|
84
|
+
records a metering event for tracking purposes.
|
|
85
|
+
|
|
86
|
+
Examples:
|
|
87
|
+
Basic usage with minimal fields:
|
|
88
|
+
```python
|
|
89
|
+
await context.memory().record_raw(
|
|
90
|
+
base={"kind": "user_action", "severity": 2},
|
|
91
|
+
text="User clicked a button."
|
|
92
|
+
)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Including metrics and additional fields:
|
|
96
|
+
```python
|
|
97
|
+
await context.memory().record_raw(
|
|
98
|
+
base={"kind": "tool_call", "stage": "execution", "severity": 3},
|
|
99
|
+
text="Tool executed successfully.",
|
|
100
|
+
metrics={"latency": 0.123, "tokens_used": 45}
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
base: A dictionary containing event fields such as `kind`, `stage`,
|
|
106
|
+
`data`, `tags`, `severity`, etc.
|
|
107
|
+
text: Optional preview text for the event. If None, it is derived
|
|
108
|
+
from the `data` field in `base`.
|
|
109
|
+
metrics: Optional dictionary of numeric metrics (e.g., latency,
|
|
110
|
+
token usage) to include in the event.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Event: The fully constructed and persisted `Event` object.
|
|
114
|
+
"""
|
|
115
|
+
ts = now_iso()
|
|
116
|
+
|
|
117
|
+
# Merge Scope dimensions
|
|
118
|
+
dims: dict[str, str] = {}
|
|
119
|
+
if self.scope is not None:
|
|
120
|
+
dims = self.scope.metering_dimensions()
|
|
121
|
+
|
|
122
|
+
run_id = base.get("run_id") or dims.get("run_id") or self.run_id
|
|
123
|
+
session_id = base.get("session_id") or dims.get("session_id") or self.session_id
|
|
124
|
+
scope_id = base.get("scope_id") or self.memory_scope_id or session_id or run_id
|
|
125
|
+
|
|
126
|
+
base.setdefault("run_id", run_id)
|
|
127
|
+
base.setdefault("scope_id", scope_id)
|
|
128
|
+
base.setdefault("session_id", session_id)
|
|
129
|
+
# ... (populate other fields from dims if needed) ...
|
|
130
|
+
|
|
131
|
+
severity = int(base.get("severity", 2))
|
|
132
|
+
signal = base.get("signal")
|
|
133
|
+
if signal is None:
|
|
134
|
+
signal = self._estimate_signal(text=text, metrics=metrics, severity=severity)
|
|
135
|
+
|
|
136
|
+
kind = base.get("kind") or "misc"
|
|
137
|
+
|
|
138
|
+
eid = stable_event_id(
|
|
139
|
+
{
|
|
140
|
+
"ts": ts,
|
|
141
|
+
"run_id": base["run_id"],
|
|
142
|
+
"kind": kind,
|
|
143
|
+
"text": (text or "")[:6000],
|
|
144
|
+
"tool": base.get("tool"),
|
|
145
|
+
}
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
evt = Event(
|
|
149
|
+
event_id=eid,
|
|
150
|
+
ts=ts,
|
|
151
|
+
run_id=run_id,
|
|
152
|
+
scope_id=scope_id,
|
|
153
|
+
kind=kind,
|
|
154
|
+
text=text,
|
|
155
|
+
data=base.get("data"),
|
|
156
|
+
tags=base.get("tags"),
|
|
157
|
+
metrics=metrics,
|
|
158
|
+
tool=base.get("tool"),
|
|
159
|
+
severity=severity,
|
|
160
|
+
signal=signal,
|
|
161
|
+
inputs=base.get("inputs"),
|
|
162
|
+
outputs=base.get("outputs"),
|
|
163
|
+
# ... pass other fields ...
|
|
164
|
+
version=2,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
await self.hotlog.append(self.timeline_id, evt, ttl_s=self.hot_ttl_s, limit=self.hot_limit)
|
|
168
|
+
await self.persistence.append_event(self.timeline_id, evt)
|
|
169
|
+
|
|
170
|
+
# Metering hook
|
|
171
|
+
try:
|
|
172
|
+
meter = current_metering()
|
|
173
|
+
await meter.record_event(scope=self.scope, scope_id=scope_id, kind=f"memory.{kind}")
|
|
174
|
+
except Exception:
|
|
175
|
+
if self.logger:
|
|
176
|
+
self.logger.exception("Error recording metering event")
|
|
177
|
+
|
|
178
|
+
return evt
|
|
179
|
+
|
|
180
|
+
async def record(
|
|
181
|
+
self,
|
|
182
|
+
kind: str,
|
|
183
|
+
data: Any,
|
|
184
|
+
tags: list[str] | None = None,
|
|
185
|
+
severity: int = 2,
|
|
186
|
+
stage: str | None = None,
|
|
187
|
+
inputs_ref=None,
|
|
188
|
+
outputs_ref=None,
|
|
189
|
+
metrics: dict[str, float] | None = None,
|
|
190
|
+
signal: float | None = None,
|
|
191
|
+
text: str | None = None, # optional override
|
|
192
|
+
) -> Event:
|
|
193
|
+
"""
|
|
194
|
+
Record an event with common fields.
|
|
195
|
+
|
|
196
|
+
This method standardizes event creation by populating fields such as
|
|
197
|
+
`kind`, `severity`, `tags`, and `metrics`. It also supports optional
|
|
198
|
+
references for inputs and outputs, and allows for signal strength
|
|
199
|
+
overrides.
|
|
200
|
+
|
|
201
|
+
Examples:
|
|
202
|
+
Basic usage for a user action:
|
|
203
|
+
```python
|
|
204
|
+
await context.memory().record(
|
|
205
|
+
kind="user_action",
|
|
206
|
+
data={"action": "clicked_button"},
|
|
207
|
+
tags=["ui", "interaction"]
|
|
208
|
+
)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Recording a tool execution with metrics:
|
|
212
|
+
```python
|
|
213
|
+
await context.memory().record(
|
|
214
|
+
kind="tool_call",
|
|
215
|
+
data={"tool": "search", "query": "weather"},
|
|
216
|
+
metrics={"latency": 0.123, "tokens_used": 45},
|
|
217
|
+
severity=3
|
|
218
|
+
)
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
kind: Logical kind of event (e.g., `"user_msg"`, `"tool_call"`, `"chat_turn"`).
|
|
223
|
+
data: JSON-serializable content or string providing event details.
|
|
224
|
+
tags: A list of string labels for categorization. Defaults to None.
|
|
225
|
+
severity: An integer (1-3) indicating importance. Defaults to 2.
|
|
226
|
+
stage: Optional stage of the event (e.g., `"user"`, `"assistant"`, `"system"`). Defaults to None.
|
|
227
|
+
inputs_ref: Optional references for input values. Defaults to None.
|
|
228
|
+
outputs_ref: Optional references for output values. Defaults to None.
|
|
229
|
+
metrics: A dictionary of numeric metrics (e.g., latency, token usage). Defaults to None.
|
|
230
|
+
signal: Manual override for the signal strength (0.0 to 1.0). If None, it is calculated heuristically.
|
|
231
|
+
text: Optional preview text override. If None, it is derived from `data`.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
Event: The fully constructed and persisted `Event` object.
|
|
235
|
+
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
# 1) derive short preview text
|
|
239
|
+
if text is None and data is not None:
|
|
240
|
+
if isinstance(data, str):
|
|
241
|
+
text = data
|
|
242
|
+
else:
|
|
243
|
+
try:
|
|
244
|
+
raw = json.dumps(data, ensure_ascii=False)
|
|
245
|
+
text = raw
|
|
246
|
+
except Exception as e:
|
|
247
|
+
text = f"<unserializable data: {e!s}>"
|
|
248
|
+
if self.logger:
|
|
249
|
+
self.logger.warning(text)
|
|
250
|
+
|
|
251
|
+
# 2) optionally truncate preview text (enforce token discipline)
|
|
252
|
+
if text and len(text) > 2000:
|
|
253
|
+
text = text[:2000] + " …[truncated]"
|
|
254
|
+
|
|
255
|
+
# 3) full structured payload in Event.data when possible
|
|
256
|
+
data_field: dict[str, Any] | None = None
|
|
257
|
+
if isinstance(data, dict):
|
|
258
|
+
data_field = data
|
|
259
|
+
elif data is not None and not isinstance(data, str):
|
|
260
|
+
# store under "value" if it's JSON-serializable
|
|
261
|
+
try:
|
|
262
|
+
json.dumps(data, ensure_ascii=False)
|
|
263
|
+
data_field = {"value": data}
|
|
264
|
+
except Exception:
|
|
265
|
+
data_field = {"repr": repr(data)}
|
|
266
|
+
|
|
267
|
+
base: dict[str, Any] = dict(
|
|
268
|
+
kind=kind,
|
|
269
|
+
stage=stage,
|
|
270
|
+
severity=severity,
|
|
271
|
+
tags=tags or [],
|
|
272
|
+
data=data_field,
|
|
273
|
+
inputs=inputs_ref,
|
|
274
|
+
outputs=outputs_ref,
|
|
275
|
+
)
|
|
276
|
+
if signal is not None:
|
|
277
|
+
base["signal"] = signal
|
|
278
|
+
|
|
279
|
+
return await self.record_raw(base=base, text=text, metrics=metrics)
|
|
280
|
+
|
|
281
|
+
def _estimate_signal(
|
|
282
|
+
self, *, text: str | None, metrics: dict[str, Any] | None, severity: int
|
|
283
|
+
) -> float:
|
|
284
|
+
score = 0.15 + 0.1 * severity
|
|
285
|
+
if text:
|
|
286
|
+
score += min(len(text) / 400.0, 0.4)
|
|
287
|
+
if metrics:
|
|
288
|
+
score += 0.2
|
|
289
|
+
return max(0.0, min(1.0, score))
|
|
290
|
+
|
|
291
|
+
async def build_prompt_segments(
|
|
292
|
+
self,
|
|
293
|
+
*,
|
|
294
|
+
recent_chat_limit: int = 12,
|
|
295
|
+
include_long_term: bool = True,
|
|
296
|
+
summary_tag: str = "session",
|
|
297
|
+
max_summaries: int = 3,
|
|
298
|
+
include_recent_tools: bool = False,
|
|
299
|
+
tool: str | None = None,
|
|
300
|
+
tool_limit: int = 10,
|
|
301
|
+
) -> dict[str, Any]:
|
|
302
|
+
"""
|
|
303
|
+
Assemble memory context for prompts, including long-term summaries,
|
|
304
|
+
recent chat history, and recent tool usage.
|
|
305
|
+
|
|
306
|
+
Examples:
|
|
307
|
+
Build prompt segments with default settings:
|
|
308
|
+
```python
|
|
309
|
+
segments = await context.memory().build_prompt_segments()
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Include recent tool usage and filter by a specific tool:
|
|
313
|
+
```python
|
|
314
|
+
segments = await context.memory().build_prompt_segments(
|
|
315
|
+
include_recent_tools=True,
|
|
316
|
+
tool="search",
|
|
317
|
+
tool_limit=5
|
|
318
|
+
)
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
recent_chat_limit: The maximum number of recent chat messages to include.
|
|
323
|
+
Defaults to 12.
|
|
324
|
+
include_long_term: Whether to include long-term memory summaries.
|
|
325
|
+
Defaults to True.
|
|
326
|
+
summary_tag: The tag used to filter long-term summaries.
|
|
327
|
+
Defaults to "session".
|
|
328
|
+
max_summaries: The maximum number of long-term summaries to include.
|
|
329
|
+
Defaults to 3.
|
|
330
|
+
include_recent_tools: Whether to include recent tool usage.
|
|
331
|
+
Defaults to False.
|
|
332
|
+
tool: The specific tool to filter recent tool usage.
|
|
333
|
+
Defaults to None.
|
|
334
|
+
tool_limit: The maximum number of recent tool events to include.
|
|
335
|
+
Defaults to 10.
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
dict[str, Any]: A dictionary containing the following keys:
|
|
339
|
+
|
|
340
|
+
- "long_term" (str): Combined long-term summary text or an empty
|
|
341
|
+
string if not included.
|
|
342
|
+
|
|
343
|
+
- "recent_chat" (list[dict[str, Any]]): A list of recent chat
|
|
344
|
+
messages, each represented as a dictionary with the following keys:
|
|
345
|
+
- "ts" (str): Timestamp of the message.
|
|
346
|
+
- "role" (str): Role of the sender (e.g., "user", "assistant").
|
|
347
|
+
- "text" (str): The content of the message.
|
|
348
|
+
- "tags" (list[str]): Tags associated with the message.
|
|
349
|
+
|
|
350
|
+
- "recent_tools" (list[dict[str, Any]]): A list of recent tool
|
|
351
|
+
usage events, each represented as a dictionary with the following keys:
|
|
352
|
+
- "ts" (str): Timestamp of the tool event.
|
|
353
|
+
- "tool" (str): Name of the tool used.
|
|
354
|
+
- "message" (str): Message or description of the tool event.
|
|
355
|
+
- "inputs" (Any): Inputs provided to the tool.
|
|
356
|
+
- "outputs" (Any): Outputs generated by the tool.
|
|
357
|
+
- "tags" (list[str]): Tags associated with the tool event.
|
|
358
|
+
"""
|
|
359
|
+
long_term_text = ""
|
|
360
|
+
if include_long_term:
|
|
361
|
+
try:
|
|
362
|
+
summaries = await self.load_recent_summaries(
|
|
363
|
+
summary_tag=summary_tag,
|
|
364
|
+
limit=max_summaries,
|
|
365
|
+
)
|
|
366
|
+
except Exception:
|
|
367
|
+
summaries = []
|
|
368
|
+
|
|
369
|
+
parts: list[str] = []
|
|
370
|
+
for s in summaries:
|
|
371
|
+
st = s.get("summary") or s.get("text") or s.get("body") or s.get("value") or ""
|
|
372
|
+
if st:
|
|
373
|
+
parts.append(st)
|
|
374
|
+
|
|
375
|
+
if parts:
|
|
376
|
+
# multiple long-term summaries → concatenate oldest→newest
|
|
377
|
+
long_term_text = "\n\n".join(parts)
|
|
378
|
+
|
|
379
|
+
recent_chat = await self.recent_chat(limit=recent_chat_limit)
|
|
380
|
+
|
|
381
|
+
recent_tools: list[dict[str, Any]] = []
|
|
382
|
+
if include_recent_tools:
|
|
383
|
+
events = await self.recent_tool_results(
|
|
384
|
+
tool=tool,
|
|
385
|
+
limit=tool_limit,
|
|
386
|
+
)
|
|
387
|
+
for e in events:
|
|
388
|
+
recent_tools.append(
|
|
389
|
+
{
|
|
390
|
+
"ts": getattr(e, "ts", None),
|
|
391
|
+
"tool": e.tool,
|
|
392
|
+
"message": e.text,
|
|
393
|
+
"inputs": getattr(e, "inputs", None),
|
|
394
|
+
"outputs": getattr(e, "outputs", None),
|
|
395
|
+
"tags": list(e.tags or []),
|
|
396
|
+
}
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
"long_term": long_term_text,
|
|
401
|
+
"recent_chat": recent_chat,
|
|
402
|
+
"recent_tools": recent_tools,
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
# ----- Stubs for future memory facade features -----
|
|
406
|
+
async def mark_event_important(
|
|
407
|
+
self,
|
|
408
|
+
event_id: str,
|
|
409
|
+
*,
|
|
410
|
+
reason: str | None = None,
|
|
411
|
+
topic: str | None = None,
|
|
412
|
+
) -> None:
|
|
413
|
+
"""
|
|
414
|
+
Stub / placeholder:
|
|
415
|
+
|
|
416
|
+
Mark a given event as "important" / "core_fact" for future policies.
|
|
417
|
+
|
|
418
|
+
Intended future behavior (not implemented yet):
|
|
419
|
+
- Look up the Event by event_id (via Persistence).
|
|
420
|
+
- Re-emit an updated Event with an added tag (e.g. "core_fact" or "pinned").
|
|
421
|
+
- Optionally promote to a fact artifact or RAG doc.
|
|
422
|
+
|
|
423
|
+
For now, this is a no-op / NotImplementedError to avoid surprise behavior.
|
|
424
|
+
"""
|
|
425
|
+
raise NotImplementedError("mark_event_important is reserved for future memory policy")
|
|
426
|
+
|
|
427
|
+
async def save_core_fact_artifact(
|
|
428
|
+
self,
|
|
429
|
+
*,
|
|
430
|
+
scope_id: str,
|
|
431
|
+
topic: str,
|
|
432
|
+
fact_id: str,
|
|
433
|
+
content: dict[str, Any],
|
|
434
|
+
):
|
|
435
|
+
"""
|
|
436
|
+
Stub / placeholder:
|
|
437
|
+
|
|
438
|
+
Save a canonical, long-lived fact as a pinned artifact.
|
|
439
|
+
Intended future behavior:
|
|
440
|
+
- Use artifacts.save_json(...) to write the fact payload under a
|
|
441
|
+
stable path like file://mem/<scope_id>/facts/<topic>/<fact_id>.json
|
|
442
|
+
- Mark the artifact pinned in the index.
|
|
443
|
+
- Optionally write a tool_result Event referencing this artifact.
|
|
444
|
+
|
|
445
|
+
Not implemented yet; provided as an explicit extension hook.
|
|
446
|
+
"""
|
|
447
|
+
raise NotImplementedError("save_core_fact_artifact is reserved for future memory policy")
|