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,204 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from typing import Any
|
|
5
|
+
from uuid import uuid4
|
|
6
|
+
|
|
7
|
+
from aethergraph.contracts.errors.errors import GraphHasPendingWaits
|
|
8
|
+
from aethergraph.contracts.services.runs import RunStore
|
|
9
|
+
from aethergraph.core.runtime.run_types import RunRecord, RunStatus
|
|
10
|
+
from aethergraph.core.runtime.runtime_metering import current_metering
|
|
11
|
+
from aethergraph.core.runtime.runtime_registry import current_registry
|
|
12
|
+
from aethergraph.services.registry.unified_registry import UnifiedRegistry
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _utcnow() -> datetime:
|
|
16
|
+
return datetime.now(tz=timezone.utc)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _is_task_graph(obj: Any) -> bool:
|
|
20
|
+
return hasattr(obj, "spec") and hasattr(obj, "io_signature")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _is_graphfn(obj: Any) -> bool:
|
|
24
|
+
from aethergraph.core.graph.graph_fn import GraphFunction # adjust path
|
|
25
|
+
|
|
26
|
+
return isinstance(obj, GraphFunction)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class RunManager:
|
|
30
|
+
"""
|
|
31
|
+
Core coordinator for runs:
|
|
32
|
+
|
|
33
|
+
- Resolves targets from the UnifiedRegistry.
|
|
34
|
+
- Calls run_or_resume_async for TaskGraph/GraphFunction.
|
|
35
|
+
- Records metadata in RunStore.
|
|
36
|
+
- TODO: (Later) can coordinate cancellation via sched_registry or best effort with graph_fn.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self, *, run_store: RunStore | None = None, registry: UnifiedRegistry | None = None
|
|
41
|
+
):
|
|
42
|
+
self._store = run_store
|
|
43
|
+
self._registry = registry
|
|
44
|
+
|
|
45
|
+
def registry(self) -> UnifiedRegistry:
|
|
46
|
+
return self._registry or current_registry()
|
|
47
|
+
|
|
48
|
+
async def _resolve_target(self, graph_id: str) -> Any:
|
|
49
|
+
reg = self.registry()
|
|
50
|
+
# Try static TaskGraph
|
|
51
|
+
try:
|
|
52
|
+
return reg.get_graph(name=graph_id, version=None)
|
|
53
|
+
except KeyError:
|
|
54
|
+
pass
|
|
55
|
+
# Try GraphFunction
|
|
56
|
+
try:
|
|
57
|
+
return reg.get_graphfn(name=graph_id, version=None)
|
|
58
|
+
except KeyError:
|
|
59
|
+
pass
|
|
60
|
+
raise KeyError(f"Graph '{graph_id}' not found")
|
|
61
|
+
|
|
62
|
+
async def start_run(
|
|
63
|
+
self,
|
|
64
|
+
graph_id: str,
|
|
65
|
+
*,
|
|
66
|
+
inputs: dict[str, Any],
|
|
67
|
+
run_id: str | None = None,
|
|
68
|
+
tags: list[str] | None = None,
|
|
69
|
+
user_id: str | None = None,
|
|
70
|
+
org_id: str | None = None,
|
|
71
|
+
) -> tuple[RunRecord, dict[str, Any] | None, bool, list[dict[str, Any]]]:
|
|
72
|
+
"""
|
|
73
|
+
The main entrypoint for the API layer.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
(record, outputs, has_waits, continuations)
|
|
77
|
+
"""
|
|
78
|
+
from aethergraph.core.runtime.graph_runner import run_or_resume_async
|
|
79
|
+
|
|
80
|
+
tags = tags or []
|
|
81
|
+
target = await self._resolve_target(graph_id)
|
|
82
|
+
rid = run_id or f"run-{uuid4().hex[:8]}"
|
|
83
|
+
|
|
84
|
+
started_at = _utcnow()
|
|
85
|
+
|
|
86
|
+
if _is_task_graph(target):
|
|
87
|
+
kind = "taskgraph"
|
|
88
|
+
elif _is_graphfn(target):
|
|
89
|
+
kind = "graphfn"
|
|
90
|
+
else:
|
|
91
|
+
kind = "other"
|
|
92
|
+
|
|
93
|
+
# Initial record
|
|
94
|
+
record = RunRecord(
|
|
95
|
+
run_id=rid,
|
|
96
|
+
graph_id=graph_id,
|
|
97
|
+
kind=kind,
|
|
98
|
+
status=RunStatus.running, # or pending, but we jump straight to running
|
|
99
|
+
started_at=started_at,
|
|
100
|
+
tags=list(tags),
|
|
101
|
+
user_id=user_id,
|
|
102
|
+
org_id=org_id,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
if self._store is not None:
|
|
106
|
+
await self._store.create(record)
|
|
107
|
+
|
|
108
|
+
outputs: dict[str, Any] | None = None
|
|
109
|
+
has_waits = False
|
|
110
|
+
continuations: list[dict[str, Any]] = []
|
|
111
|
+
error_msg: str | None = None
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
result = await run_or_resume_async(target, inputs or {}, run_id=rid)
|
|
115
|
+
# If we get here without GraphHasPendingWaits, run is completed
|
|
116
|
+
outputs = result if isinstance(result, dict) else {"result": result}
|
|
117
|
+
record.status = RunStatus.succeeded
|
|
118
|
+
record.finished_at = _utcnow()
|
|
119
|
+
|
|
120
|
+
except GraphHasPendingWaits as e:
|
|
121
|
+
# Graph quiesced with pending waits
|
|
122
|
+
record.status = RunStatus.running
|
|
123
|
+
has_waits = True
|
|
124
|
+
continuations = getattr(e, "continuations", [])
|
|
125
|
+
# outputs stay None
|
|
126
|
+
|
|
127
|
+
except Exception as exc:
|
|
128
|
+
record.status = RunStatus.failed
|
|
129
|
+
record.finished_at = _utcnow()
|
|
130
|
+
error_msg = str(exc)
|
|
131
|
+
record.error = error_msg
|
|
132
|
+
# TODO: log here with current_logger_factory if desired
|
|
133
|
+
import logging
|
|
134
|
+
|
|
135
|
+
logging.getLogger("aethergraph.runtime.run_manager").exception(
|
|
136
|
+
"Run %s failed with exception with %s", rid, error_msg
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if self._store is not None:
|
|
140
|
+
await self._store.update_status(
|
|
141
|
+
rid,
|
|
142
|
+
record.status,
|
|
143
|
+
finished_at=record.finished_at,
|
|
144
|
+
error=error_msg,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
meter = current_metering()
|
|
148
|
+
# Duration: if finished_at is None, use now()
|
|
149
|
+
finished_at = record.finished_at or _utcnow()
|
|
150
|
+
duration_s = (finished_at - started_at).total_seconds()
|
|
151
|
+
# Map Runstatus + waits to status string for metering
|
|
152
|
+
if has_waits:
|
|
153
|
+
meter_status = "waiting"
|
|
154
|
+
else:
|
|
155
|
+
status_str = getattr(record.status, "value", str(record.status))
|
|
156
|
+
meter_status = status_str
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
await meter.record_run(
|
|
160
|
+
user_id=user_id,
|
|
161
|
+
org_id=org_id,
|
|
162
|
+
run_id=rid,
|
|
163
|
+
graph_id=graph_id,
|
|
164
|
+
status=meter_status,
|
|
165
|
+
duration_s=duration_s,
|
|
166
|
+
)
|
|
167
|
+
except Exception:
|
|
168
|
+
# Never fail the run due to metering issues
|
|
169
|
+
import logging
|
|
170
|
+
|
|
171
|
+
logging.getLogger("aethergraph.runtime.run_manager").exception(
|
|
172
|
+
"Error recording run metering for run_id=%s", rid
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
return record, outputs, has_waits, continuations
|
|
176
|
+
|
|
177
|
+
async def get_record(self, run_id: str) -> RunRecord | None:
|
|
178
|
+
if self._store is None:
|
|
179
|
+
return None
|
|
180
|
+
return await self._store.get(run_id)
|
|
181
|
+
|
|
182
|
+
async def list_records(
|
|
183
|
+
self,
|
|
184
|
+
*,
|
|
185
|
+
graph_id: str | None = None,
|
|
186
|
+
status: RunStatus | None = None,
|
|
187
|
+
limit: int = 100,
|
|
188
|
+
) -> list[RunRecord]:
|
|
189
|
+
if self._store is None:
|
|
190
|
+
return []
|
|
191
|
+
return await self._store.list(graph_id=graph_id, status=status, limit=limit)
|
|
192
|
+
|
|
193
|
+
# Placeholder for future cancellation
|
|
194
|
+
async def cancel_run(self, run_id: str) -> RunRecord | None:
|
|
195
|
+
"""
|
|
196
|
+
Later: use container.sched_registry to find scheduler and request cancellation.
|
|
197
|
+
|
|
198
|
+
For now, it's a stub that just reads the current record.
|
|
199
|
+
"""
|
|
200
|
+
# Future:
|
|
201
|
+
# - container = current_services()
|
|
202
|
+
# - sched = container.sched_registry.get(run_id)
|
|
203
|
+
# - if sched: sched.request_cancel() or sched.terminate()
|
|
204
|
+
return await self.get_record(run_id)
|
|
@@ -17,8 +17,8 @@ class RunRegistrationGuard(AbstractContextManager):
|
|
|
17
17
|
reg = self.container.sched_registry
|
|
18
18
|
existing = reg.get(self.run_id)
|
|
19
19
|
if existing is not None and existing is not self.scheduler:
|
|
20
|
-
#
|
|
21
|
-
|
|
20
|
+
# Do nothing if already registered
|
|
21
|
+
return self
|
|
22
22
|
reg.register(self.run_id, self.scheduler)
|
|
23
23
|
self._did_reg = True
|
|
24
24
|
return self
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
# used to represent the status of a run, primiarily used in endpoint with RunManager
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RunStatus(str, Enum):
|
|
10
|
+
pending = "pending"
|
|
11
|
+
running = "running"
|
|
12
|
+
succeeded = "succeeded"
|
|
13
|
+
failed = "failed"
|
|
14
|
+
waiting = "waiting"
|
|
15
|
+
canceled = "canceled"
|
|
16
|
+
cancellation_requested = "cancellation_requested"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RunOrigin(str, Enum):
|
|
20
|
+
app = "app" # launched from an application UI
|
|
21
|
+
agent = "agent" # launched by an AI agent
|
|
22
|
+
chat = "chat" # launched from a chat interface
|
|
23
|
+
playground = "playground" # launched from a playground environment (sidecar/SDK/local dev)
|
|
24
|
+
api = "api" # launched from an API call
|
|
25
|
+
system = "system" # launched from a system process (internal maintenance, cron job, etc.)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class RunVisibility(str, Enum):
|
|
29
|
+
normal = "normal" # visible to all users with access to the org/client
|
|
30
|
+
inline = "inline" # hidden from run listings, only shown in session / debug views
|
|
31
|
+
hidden = "hidden" # hidden from all UIs (used in quick tests, ephemeral runs, etc.)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class RunImportance(str, Enum):
|
|
35
|
+
normal = "normal" # standard run
|
|
36
|
+
ephemeral = "ephemeral" # low-importance run, noisy or temporary (may be pruned sooner)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class RunRecord:
|
|
41
|
+
"""
|
|
42
|
+
Core-level representation of a run.
|
|
43
|
+
|
|
44
|
+
This is independent from any Pydantic model used by the HTTP API.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
run_id: str
|
|
48
|
+
graph_id: str
|
|
49
|
+
kind: str # "taskgraph" | "graphfn" | other in the future
|
|
50
|
+
status: RunStatus
|
|
51
|
+
started_at: datetime
|
|
52
|
+
finished_at: datetime | None = None
|
|
53
|
+
|
|
54
|
+
tags: list[str] = field(default_factory=list)
|
|
55
|
+
user_id: str | None = None
|
|
56
|
+
org_id: str | None = None
|
|
57
|
+
error: str | None = None
|
|
58
|
+
meta: dict[str, Any] = field(default_factory=dict)
|
|
59
|
+
|
|
60
|
+
session_id: str | None = None
|
|
61
|
+
origin: RunOrigin = RunOrigin.app
|
|
62
|
+
visibility: RunVisibility = RunVisibility.normal
|
|
63
|
+
importance: RunImportance = RunImportance.normal
|
|
64
|
+
|
|
65
|
+
# optional agent/app linkage
|
|
66
|
+
agent_id: str | None = None
|
|
67
|
+
app_id: str | None = None
|
|
68
|
+
|
|
69
|
+
# Artifact statistics
|
|
70
|
+
artifact_count: int = 0
|
|
71
|
+
first_artifact_at: datetime | None = None
|
|
72
|
+
last_artifact_at: datetime | None = None
|
|
73
|
+
|
|
74
|
+
# Optional: keep a small rolling window of recent artifact IDs
|
|
75
|
+
recent_artifact_ids: list[str] = field(default_factory=list)
|
|
76
|
+
|
|
77
|
+
def __item__(self, key: str) -> Any:
|
|
78
|
+
return getattr(self, key)
|
|
79
|
+
|
|
80
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
81
|
+
return getattr(self, key, default)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# Session-related run types
|
|
85
|
+
class SessionKind(str, Enum):
|
|
86
|
+
chat = "chat"
|
|
87
|
+
playground = "playground"
|
|
88
|
+
notebook = "notebook"
|
|
89
|
+
pipline = "pipeline" # future
|
|
@@ -2,9 +2,11 @@ from collections.abc import Callable
|
|
|
2
2
|
from dataclasses import dataclass, field
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
|
+
from aethergraph.api.v1.deps import RequestIdentity
|
|
6
|
+
from aethergraph.contracts.storage.artifact_index import AsyncArtifactIndex
|
|
7
|
+
|
|
5
8
|
# ---- artifact services ----
|
|
6
|
-
from aethergraph.
|
|
7
|
-
from aethergraph.services.artifacts.jsonl_index import JsonlArtifactIndex # AsyncArtifactIndex
|
|
9
|
+
from aethergraph.contracts.storage.artifact_store import AsyncArtifactStore
|
|
8
10
|
|
|
9
11
|
# ---- channel services ----
|
|
10
12
|
from aethergraph.services.channel.channel_bus import ChannelBus
|
|
@@ -16,7 +18,9 @@ from aethergraph.services.continuations.stores.fs_store import (
|
|
|
16
18
|
|
|
17
19
|
# ---- memory services ----
|
|
18
20
|
from aethergraph.services.memory.facade import MemoryFacade
|
|
21
|
+
from aethergraph.services.rag.node_rag import NodeRAG
|
|
19
22
|
from aethergraph.services.resume.router import ResumeRouter
|
|
23
|
+
from aethergraph.services.viz.facade import VizFacade
|
|
20
24
|
from aethergraph.services.waits.wait_registry import WaitRegistry
|
|
21
25
|
|
|
22
26
|
from ..graph.task_node import TaskNodeRuntime
|
|
@@ -31,9 +35,15 @@ class RuntimeEnv:
|
|
|
31
35
|
|
|
32
36
|
run_id: str
|
|
33
37
|
graph_id: str | None = None
|
|
38
|
+
session_id: str | None = None
|
|
39
|
+
identity: RequestIdentity | None = None
|
|
34
40
|
graph_inputs: dict[str, Any] = field(default_factory=dict)
|
|
35
41
|
outputs_by_node: dict[str, dict[str, Any]] = field(default_factory=dict)
|
|
36
42
|
|
|
43
|
+
# agent and app ids
|
|
44
|
+
agent_id: str | None = None # for agent-invoked runs
|
|
45
|
+
app_id: str | None = None # for app-invoked runs
|
|
46
|
+
|
|
37
47
|
# container (DI)
|
|
38
48
|
container: DefaultContainer = field(default_factory=get_container)
|
|
39
49
|
|
|
@@ -70,11 +80,11 @@ class RuntimeEnv:
|
|
|
70
80
|
return self.container.wait_registry
|
|
71
81
|
|
|
72
82
|
@property
|
|
73
|
-
def artifacts(self) ->
|
|
83
|
+
def artifacts(self) -> AsyncArtifactStore:
|
|
74
84
|
return self.container.artifacts
|
|
75
85
|
|
|
76
86
|
@property
|
|
77
|
-
def artifact_index(self) ->
|
|
87
|
+
def artifact_index(self) -> AsyncArtifactIndex:
|
|
78
88
|
return self.container.artifact_index
|
|
79
89
|
|
|
80
90
|
@property
|
|
@@ -104,15 +114,43 @@ class RuntimeEnv:
|
|
|
104
114
|
"run_id": self.run_id,
|
|
105
115
|
"graph_id": self.graph_id,
|
|
106
116
|
"node_id": node.node_id,
|
|
107
|
-
"agent_id": getattr(node, "tool_name", None),
|
|
108
117
|
"tags": [],
|
|
109
118
|
"entities": [],
|
|
110
119
|
}
|
|
120
|
+
|
|
121
|
+
level, custom_scope_id = self._resolve_memory_config()
|
|
122
|
+
mem_scope = (
|
|
123
|
+
self.container.scope_factory.for_memory(
|
|
124
|
+
identity=self.identity,
|
|
125
|
+
run_id=self.run_id,
|
|
126
|
+
graph_id=self.graph_id,
|
|
127
|
+
node_id=node.node_id,
|
|
128
|
+
session_id=self.session_id,
|
|
129
|
+
level=level,
|
|
130
|
+
custom_scope_id=custom_scope_id,
|
|
131
|
+
)
|
|
132
|
+
if self.container.scope_factory
|
|
133
|
+
else None
|
|
134
|
+
)
|
|
135
|
+
|
|
111
136
|
mem: MemoryFacade = self.memory_factory.for_session(
|
|
112
137
|
run_id=self.run_id,
|
|
113
138
|
graph_id=self.graph_id,
|
|
114
139
|
node_id=node.node_id,
|
|
115
|
-
|
|
140
|
+
session_id=self.session_id,
|
|
141
|
+
scope=mem_scope,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
node_scope = (
|
|
145
|
+
self.container.scope_factory.for_node(
|
|
146
|
+
identity=self.identity,
|
|
147
|
+
run_id=self.run_id,
|
|
148
|
+
graph_id=self.graph_id,
|
|
149
|
+
node_id=node.node_id,
|
|
150
|
+
session_id=self.session_id,
|
|
151
|
+
)
|
|
152
|
+
if self.container.scope_factory
|
|
153
|
+
else None
|
|
116
154
|
)
|
|
117
155
|
|
|
118
156
|
from aethergraph.services.artifacts.facade import ArtifactFacade
|
|
@@ -125,8 +163,30 @@ class RuntimeEnv:
|
|
|
125
163
|
tool_version=node.tool_version, # to be filled from node if available
|
|
126
164
|
store=self.artifacts,
|
|
127
165
|
index=self.artifact_index,
|
|
166
|
+
scope=node_scope,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# ------- Viz Service tied to this node/run -------'
|
|
170
|
+
vis_facade = VizFacade(
|
|
171
|
+
run_id=self.run_id,
|
|
172
|
+
graph_id=self.graph_id,
|
|
173
|
+
node_id=node.node_id,
|
|
174
|
+
tool_name=node.tool_name,
|
|
175
|
+
tool_version=node.tool_version,
|
|
176
|
+
artifacts=artifact_facade,
|
|
177
|
+
viz_service=self.container.viz_service,
|
|
178
|
+
scope=node_scope,
|
|
128
179
|
)
|
|
129
180
|
|
|
181
|
+
# ------- RAG Facade in Memory tied to this node/run -------'
|
|
182
|
+
rag_for_node = None
|
|
183
|
+
if self.rag_facade is not None and node_scope is not None:
|
|
184
|
+
rag_for_node = NodeRAG(
|
|
185
|
+
rag=self.rag_facade,
|
|
186
|
+
scope=node_scope,
|
|
187
|
+
default_scope_id=(mem_scope.memory_scope_id() if mem_scope else None),
|
|
188
|
+
)
|
|
189
|
+
|
|
130
190
|
services = NodeServices(
|
|
131
191
|
channels=self.channels,
|
|
132
192
|
continuation_store=self.continuation_store,
|
|
@@ -137,13 +197,19 @@ class RuntimeEnv:
|
|
|
137
197
|
kv=self.container.kv_hot, # keep using hot kv for ephemeral
|
|
138
198
|
memory=self.memory_factory, # factory (for other sessions if needed)
|
|
139
199
|
memory_facade=mem, # bound memory for this run/node
|
|
200
|
+
viz=vis_facade,
|
|
140
201
|
llm=self.llm_service, # LLMService
|
|
141
|
-
rag=
|
|
202
|
+
rag=rag_for_node, # RAGService
|
|
142
203
|
mcp=self.mcp_service, # MCPService
|
|
204
|
+
run_manager=self.container.run_manager, # RunManager
|
|
143
205
|
)
|
|
144
206
|
return ExecutionContext(
|
|
145
207
|
run_id=self.run_id,
|
|
208
|
+
session_id=self.session_id,
|
|
209
|
+
identity=self.identity,
|
|
146
210
|
graph_id=self.graph_id,
|
|
211
|
+
agent_id=self.agent_id,
|
|
212
|
+
app_id=self.app_id,
|
|
147
213
|
graph_inputs=self.graph_inputs,
|
|
148
214
|
outputs_by_node=self.outputs_by_node,
|
|
149
215
|
services=services,
|
|
@@ -151,7 +217,70 @@ class RuntimeEnv:
|
|
|
151
217
|
clock=self.clock,
|
|
152
218
|
resume_payload=resume_payload,
|
|
153
219
|
should_run_fn=self.should_run_fn,
|
|
220
|
+
scope=node_scope,
|
|
154
221
|
# Back-compat shim for old ctx.mem()
|
|
155
222
|
bound_memory=BoundMemoryAdapter(mem, defaults),
|
|
156
223
|
resume_router=self.resume_router,
|
|
157
224
|
)
|
|
225
|
+
|
|
226
|
+
def _resolve_memory_config(self) -> tuple[str, str | None]:
|
|
227
|
+
"""
|
|
228
|
+
Returns (level, custom_scope_id).
|
|
229
|
+
|
|
230
|
+
Resolution order:
|
|
231
|
+
1) If this run has an agent_id, read from the agent registry meta.
|
|
232
|
+
2) Else if this run has an app_id, read from the app registry meta.
|
|
233
|
+
3) Else fall back to graph/graphfn meta.
|
|
234
|
+
4) Defaults:
|
|
235
|
+
- agent/app-backed runs -> "session"
|
|
236
|
+
- plain graph runs -> "run"
|
|
237
|
+
"""
|
|
238
|
+
registry = self.registry
|
|
239
|
+
level: str = "session" # safe default
|
|
240
|
+
custom_scope_id: str | None = None
|
|
241
|
+
meta: dict[str, Any] = {}
|
|
242
|
+
|
|
243
|
+
if registry:
|
|
244
|
+
# Prefer agent meta
|
|
245
|
+
if self.agent_id:
|
|
246
|
+
meta = (
|
|
247
|
+
registry.get_meta(
|
|
248
|
+
nspace="agent",
|
|
249
|
+
name=self.agent_id,
|
|
250
|
+
version=None,
|
|
251
|
+
)
|
|
252
|
+
or {}
|
|
253
|
+
)
|
|
254
|
+
# Then app meta
|
|
255
|
+
elif self.app_id:
|
|
256
|
+
meta = (
|
|
257
|
+
registry.get_meta(
|
|
258
|
+
nspace="app",
|
|
259
|
+
name=self.app_id,
|
|
260
|
+
version=None,
|
|
261
|
+
)
|
|
262
|
+
or {}
|
|
263
|
+
)
|
|
264
|
+
# Finally, bare graph meta (graphfn or taskgraph)
|
|
265
|
+
elif self.graph_id:
|
|
266
|
+
meta = (
|
|
267
|
+
registry.get_meta("graphfn", self.graph_id, None)
|
|
268
|
+
or registry.get_meta("graph", self.graph_id, None)
|
|
269
|
+
or {}
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
if meta:
|
|
273
|
+
# Top-level keys from as_agent/as_app extras
|
|
274
|
+
if "memory_level" in meta:
|
|
275
|
+
level = meta["memory_level"]
|
|
276
|
+
else:
|
|
277
|
+
# Fallback by kind if not explicitly set
|
|
278
|
+
kind = meta.get("kind")
|
|
279
|
+
level = "session" if kind == "agent" else "run"
|
|
280
|
+
|
|
281
|
+
custom_scope_id = meta.get("memory_scope")
|
|
282
|
+
else:
|
|
283
|
+
# If we have an agent_id but no meta, still bias to session-level
|
|
284
|
+
level = "session" if self.agent_id else "run"
|
|
285
|
+
|
|
286
|
+
return level, custom_scope_id
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from contextvars import ContextVar
|
|
2
|
+
|
|
3
|
+
from aethergraph.contracts.services.metering import MeteringService
|
|
4
|
+
from aethergraph.services.metering.noop import NoopMeteringService
|
|
5
|
+
|
|
6
|
+
MeterContext = dict[str, str | None]
|
|
7
|
+
current_meter_context: ContextVar[MeterContext] = ContextVar("ag_meter_context", default={})
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Process-wide default (can be replaced during app startup)
|
|
11
|
+
__singleton_metering: MeteringService = NoopMeteringService()
|
|
12
|
+
|
|
13
|
+
# Optional per-context override
|
|
14
|
+
_current_metering: ContextVar[MeteringService | None] = ContextVar("ag_metering", default=None)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def install_global_metering(svc: MeteringService) -> None:
|
|
18
|
+
"""
|
|
19
|
+
Called at server startup to install the real metering service.
|
|
20
|
+
|
|
21
|
+
E.g. in create_app():
|
|
22
|
+
install_global_metering(EventLogMeteringService(meter_store))
|
|
23
|
+
"""
|
|
24
|
+
global __singleton_metering
|
|
25
|
+
__singleton_metering = svc
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def set_current_metering(svc: MeteringService) -> None:
|
|
29
|
+
"""
|
|
30
|
+
Override the metering service for the current context (tests, special scopes).
|
|
31
|
+
"""
|
|
32
|
+
_current_metering.set(svc)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def global_metering() -> MeteringService:
|
|
36
|
+
"""
|
|
37
|
+
Return the process-wide singleton (usually a real service after startup,
|
|
38
|
+
or NoopMeteringService in CLI/tests).
|
|
39
|
+
"""
|
|
40
|
+
return __singleton_metering
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def current_metering() -> MeteringService:
|
|
44
|
+
"""
|
|
45
|
+
Get the current metering service.
|
|
46
|
+
|
|
47
|
+
Priority:
|
|
48
|
+
1) Container services (if installed) and they hold a .metering
|
|
49
|
+
2) ContextVar override (set_current_metering)
|
|
50
|
+
3) Global singleton (Noop by default)
|
|
51
|
+
"""
|
|
52
|
+
from ..runtime.runtime_services import current_services # lazy import
|
|
53
|
+
|
|
54
|
+
# 1) Prefer container services.metering if present
|
|
55
|
+
try:
|
|
56
|
+
svc_container = current_services()
|
|
57
|
+
svc = getattr(svc_container, "metering", None)
|
|
58
|
+
# install the metering from services container
|
|
59
|
+
set_current_metering(svc)
|
|
60
|
+
if isinstance(svc, MeteringService.__constraints__): # type: ignore[attr-defined]
|
|
61
|
+
return svc
|
|
62
|
+
except Exception:
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
# 2) ContextVar
|
|
66
|
+
svc = _current_metering.get()
|
|
67
|
+
if svc is not None:
|
|
68
|
+
return svc
|
|
69
|
+
|
|
70
|
+
# 3) Fallback
|
|
71
|
+
return __singleton_metering
|
|
@@ -2,31 +2,54 @@ from contextvars import ContextVar
|
|
|
2
2
|
|
|
3
3
|
from aethergraph.services.registry.unified_registry import UnifiedRegistry
|
|
4
4
|
|
|
5
|
+
# Single process-wide registry instance
|
|
5
6
|
__singleton_registry: UnifiedRegistry = UnifiedRegistry()
|
|
7
|
+
|
|
8
|
+
# Optional overrides per-context (rarely needed)
|
|
6
9
|
_current_registry: ContextVar[UnifiedRegistry | None] = ContextVar("ag_registry", default=None)
|
|
7
10
|
|
|
8
11
|
|
|
9
|
-
def
|
|
10
|
-
"""
|
|
12
|
+
def global_registry() -> UnifiedRegistry:
|
|
13
|
+
"""
|
|
14
|
+
Return the process-wide global registry instance.
|
|
15
|
+
|
|
16
|
+
Use this when you explicitly want the singleton, e.g. wiring into containers.
|
|
17
|
+
"""
|
|
18
|
+
return __singleton_registry
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def set_current_registry(reg: UnifiedRegistry) -> None:
|
|
22
|
+
"""
|
|
23
|
+
Override the registry for the current context (e.g., tests or special scopes).
|
|
24
|
+
"""
|
|
11
25
|
_current_registry.set(reg)
|
|
12
26
|
|
|
13
27
|
|
|
14
28
|
def current_registry() -> UnifiedRegistry:
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
|
|
29
|
+
"""
|
|
30
|
+
Get the current registry.
|
|
31
|
+
|
|
32
|
+
Priority:
|
|
33
|
+
1) Container services (if installed) and they hold a registry.
|
|
34
|
+
2) ContextVar override (set_current_registry).
|
|
35
|
+
3) Global singleton.
|
|
36
|
+
"""
|
|
37
|
+
from .runtime_services import current_services # lazy import to avoid cycles
|
|
18
38
|
|
|
19
|
-
|
|
39
|
+
# 1) If services are installed and have a registry, prefer that
|
|
20
40
|
try:
|
|
21
|
-
# get current services and registry from there
|
|
22
41
|
svc = current_services()
|
|
23
|
-
|
|
24
|
-
|
|
42
|
+
reg = getattr(svc, "registry", None)
|
|
43
|
+
if isinstance(reg, UnifiedRegistry):
|
|
44
|
+
return reg
|
|
25
45
|
except Exception:
|
|
46
|
+
# services not installed or not accessible in this context
|
|
26
47
|
pass
|
|
27
48
|
|
|
28
|
-
#
|
|
49
|
+
# 2) ContextVar
|
|
29
50
|
reg = _current_registry.get()
|
|
30
|
-
if reg is None:
|
|
31
|
-
return
|
|
32
|
-
|
|
51
|
+
if reg is not None:
|
|
52
|
+
return reg
|
|
53
|
+
|
|
54
|
+
# 3) Fallback to singleton
|
|
55
|
+
return __singleton_registry
|