aethergraph 0.1.0a2__py3-none-any.whl → 0.1.0a4__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/__main__.py +3 -0
- aethergraph/api/v1/artifacts.py +23 -4
- aethergraph/api/v1/schemas.py +7 -0
- aethergraph/api/v1/session.py +123 -4
- aethergraph/config/config.py +2 -0
- aethergraph/config/search.py +49 -0
- aethergraph/contracts/services/channel.py +18 -1
- aethergraph/contracts/services/execution.py +58 -0
- aethergraph/contracts/services/llm.py +26 -0
- aethergraph/contracts/services/memory.py +10 -4
- aethergraph/contracts/services/planning.py +53 -0
- aethergraph/contracts/storage/event_log.py +8 -0
- aethergraph/contracts/storage/search_backend.py +47 -0
- aethergraph/contracts/storage/vector_index.py +73 -0
- aethergraph/core/graph/action_spec.py +76 -0
- aethergraph/core/graph/graph_fn.py +75 -2
- aethergraph/core/graph/graphify.py +74 -2
- aethergraph/core/runtime/graph_runner.py +2 -1
- aethergraph/core/runtime/node_context.py +66 -3
- aethergraph/core/runtime/node_services.py +8 -0
- aethergraph/core/runtime/run_manager.py +263 -271
- aethergraph/core/runtime/run_types.py +54 -1
- aethergraph/core/runtime/runtime_env.py +35 -14
- aethergraph/core/runtime/runtime_services.py +308 -18
- aethergraph/plugins/agents/default_chat_agent.py +266 -74
- aethergraph/plugins/agents/default_chat_agent_v2.py +487 -0
- aethergraph/plugins/channel/adapters/webui.py +69 -21
- aethergraph/plugins/channel/routes/webui_routes.py +8 -48
- aethergraph/runtime/__init__.py +12 -0
- aethergraph/server/app_factory.py +10 -1
- aethergraph/server/ui_static/assets/index-CFktGdbW.js +4913 -0
- aethergraph/server/ui_static/assets/index-DcfkFlTA.css +1 -0
- aethergraph/server/ui_static/index.html +2 -2
- aethergraph/services/artifacts/facade.py +157 -21
- aethergraph/services/artifacts/types.py +35 -0
- aethergraph/services/artifacts/utils.py +42 -0
- aethergraph/services/channel/channel_bus.py +3 -1
- aethergraph/services/channel/event_hub copy.py +55 -0
- aethergraph/services/channel/event_hub.py +81 -0
- aethergraph/services/channel/factory.py +3 -2
- aethergraph/services/channel/session.py +709 -74
- aethergraph/services/container/default_container.py +69 -7
- aethergraph/services/execution/__init__.py +0 -0
- aethergraph/services/execution/local_python.py +118 -0
- aethergraph/services/indices/__init__.py +0 -0
- aethergraph/services/indices/global_indices.py +21 -0
- aethergraph/services/indices/scoped_indices.py +292 -0
- aethergraph/services/llm/generic_client.py +342 -46
- aethergraph/services/llm/generic_embed_client.py +359 -0
- aethergraph/services/llm/types.py +3 -1
- aethergraph/services/memory/distillers/llm_long_term.py +60 -109
- aethergraph/services/memory/distillers/llm_long_term_v1.py +180 -0
- aethergraph/services/memory/distillers/llm_meta_summary.py +57 -266
- aethergraph/services/memory/distillers/llm_meta_summary_v1.py +342 -0
- aethergraph/services/memory/distillers/long_term.py +48 -131
- aethergraph/services/memory/distillers/long_term_v1.py +170 -0
- aethergraph/services/memory/facade/chat.py +18 -8
- aethergraph/services/memory/facade/core.py +159 -19
- aethergraph/services/memory/facade/distillation.py +86 -31
- aethergraph/services/memory/facade/retrieval.py +100 -1
- aethergraph/services/memory/factory.py +4 -1
- aethergraph/services/planning/__init__.py +0 -0
- aethergraph/services/planning/action_catalog.py +271 -0
- aethergraph/services/planning/bindings.py +56 -0
- aethergraph/services/planning/dependency_index.py +65 -0
- aethergraph/services/planning/flow_validator.py +263 -0
- aethergraph/services/planning/graph_io_adapter.py +150 -0
- aethergraph/services/planning/input_parser.py +312 -0
- aethergraph/services/planning/missing_inputs.py +28 -0
- aethergraph/services/planning/node_planner.py +613 -0
- aethergraph/services/planning/orchestrator.py +112 -0
- aethergraph/services/planning/plan_executor.py +506 -0
- aethergraph/services/planning/plan_types.py +321 -0
- aethergraph/services/planning/planner.py +617 -0
- aethergraph/services/planning/planner_service.py +369 -0
- aethergraph/services/planning/planning_context_builder.py +43 -0
- aethergraph/services/planning/quick_actions.py +29 -0
- aethergraph/services/planning/routers/__init__.py +0 -0
- aethergraph/services/planning/routers/simple_router.py +26 -0
- aethergraph/services/rag/facade.py +0 -3
- aethergraph/services/scope/scope.py +30 -30
- aethergraph/services/scope/scope_factory.py +15 -7
- aethergraph/services/skills/__init__.py +0 -0
- aethergraph/services/skills/skill_registry.py +465 -0
- aethergraph/services/skills/skills.py +220 -0
- aethergraph/services/skills/utils.py +194 -0
- aethergraph/storage/artifacts/artifact_index_jsonl.py +16 -10
- aethergraph/storage/artifacts/artifact_index_sqlite.py +12 -2
- aethergraph/storage/docstore/sqlite_doc_sync.py +1 -1
- aethergraph/storage/memory/event_persist.py +42 -2
- aethergraph/storage/memory/fs_persist.py +32 -2
- aethergraph/storage/search_backend/__init__.py +0 -0
- aethergraph/storage/search_backend/generic_vector_backend.py +230 -0
- aethergraph/storage/search_backend/null_backend.py +34 -0
- aethergraph/storage/search_backend/sqlite_lexical_backend.py +387 -0
- aethergraph/storage/search_backend/utils.py +31 -0
- aethergraph/storage/search_factory.py +75 -0
- aethergraph/storage/vector_index/faiss_index.py +72 -4
- aethergraph/storage/vector_index/sqlite_index.py +521 -52
- aethergraph/storage/vector_index/sqlite_index_vanila.py +311 -0
- aethergraph/storage/vector_index/utils.py +22 -0
- {aethergraph-0.1.0a2.dist-info → aethergraph-0.1.0a4.dist-info}/METADATA +1 -1
- {aethergraph-0.1.0a2.dist-info → aethergraph-0.1.0a4.dist-info}/RECORD +108 -64
- {aethergraph-0.1.0a2.dist-info → aethergraph-0.1.0a4.dist-info}/WHEEL +1 -1
- aethergraph/plugins/agents/default_chat_agent copy.py +0 -90
- aethergraph/server/ui_static/assets/index-BR5GtXcZ.css +0 -1
- aethergraph/server/ui_static/assets/index-CQ0HZZ83.js +0 -400
- aethergraph/services/eventhub/event_hub.py +0 -76
- aethergraph/services/llm/generic_client copy.py +0 -691
- aethergraph/services/prompts/file_store.py +0 -41
- {aethergraph-0.1.0a2.dist-info → aethergraph-0.1.0a4.dist-info}/entry_points.txt +0 -0
- {aethergraph-0.1.0a2.dist-info → aethergraph-0.1.0a4.dist-info}/licenses/LICENSE +0 -0
- {aethergraph-0.1.0a2.dist-info → aethergraph-0.1.0a4.dist-info}/licenses/NOTICE +0 -0
- {aethergraph-0.1.0a2.dist-info → aethergraph-0.1.0a4.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
from dataclasses import dataclass, field
|
|
1
|
+
from dataclasses import asdict, dataclass, field, is_dataclass
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
from enum import Enum
|
|
4
|
+
import json
|
|
4
5
|
from typing import Any
|
|
5
6
|
|
|
6
7
|
# used to represent the status of a run, primiarily used in endpoint with RunManager
|
|
@@ -87,3 +88,55 @@ class SessionKind(str, Enum):
|
|
|
87
88
|
playground = "playground"
|
|
88
89
|
notebook = "notebook"
|
|
89
90
|
pipline = "pipeline" # future
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
MAX_PREVIEW_CHARS = 16_000 # tune as you like
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _to_json_ish(obj: Any) -> Any:
|
|
97
|
+
"""
|
|
98
|
+
Best-effort conversion of arbitrary Python objects to something JSON-ish.
|
|
99
|
+
This is ONLY for UI preview, never for planner semantics.
|
|
100
|
+
"""
|
|
101
|
+
if obj is None:
|
|
102
|
+
return None
|
|
103
|
+
if isinstance(obj, (str, int, float, bool)): # noqa: UP038
|
|
104
|
+
return obj
|
|
105
|
+
if isinstance(obj, (list, tuple, set)): # noqa: UP038
|
|
106
|
+
return [_to_json_ish(x) for x in obj]
|
|
107
|
+
if isinstance(obj, dict):
|
|
108
|
+
return {str(k): _to_json_ish(v) for k, v in obj.items()}
|
|
109
|
+
if is_dataclass(obj):
|
|
110
|
+
return _to_json_ish(asdict(obj))
|
|
111
|
+
|
|
112
|
+
# Optional: numpy handling
|
|
113
|
+
try:
|
|
114
|
+
import numpy as np # type: ignore
|
|
115
|
+
|
|
116
|
+
if isinstance(obj, np.ndarray):
|
|
117
|
+
return {
|
|
118
|
+
"__ndarray__": True,
|
|
119
|
+
"shape": obj.shape,
|
|
120
|
+
"dtype": str(obj.dtype),
|
|
121
|
+
"preview": _to_json_ish(obj.flatten()[:10].tolist()),
|
|
122
|
+
}
|
|
123
|
+
except Exception:
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
return repr(obj)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _make_preview(obj: Any, max_chars: int = MAX_PREVIEW_CHARS) -> tuple[str, bool]:
|
|
130
|
+
"""
|
|
131
|
+
Returns (preview_str, truncated_flag).
|
|
132
|
+
Never used for planning; only for UI.
|
|
133
|
+
"""
|
|
134
|
+
jsonish = _to_json_ish(obj)
|
|
135
|
+
try:
|
|
136
|
+
s = json.dumps(jsonish, ensure_ascii=False)
|
|
137
|
+
except TypeError:
|
|
138
|
+
s = repr(jsonish)
|
|
139
|
+
truncated = len(s) > max_chars
|
|
140
|
+
if truncated:
|
|
141
|
+
s = s[:max_chars] + "... (truncated)"
|
|
142
|
+
return s, truncated
|
|
@@ -17,6 +17,7 @@ from aethergraph.services.continuations.stores.fs_store import (
|
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
# ---- memory services ----
|
|
20
|
+
from aethergraph.services.indices.scoped_indices import ScopedIndices
|
|
20
21
|
from aethergraph.services.memory.facade import MemoryFacade
|
|
21
22
|
from aethergraph.services.rag.node_rag import NodeRAG
|
|
22
23
|
from aethergraph.services.resume.router import ResumeRouter
|
|
@@ -118,6 +119,18 @@ class RuntimeEnv:
|
|
|
118
119
|
"entities": [],
|
|
119
120
|
}
|
|
120
121
|
|
|
122
|
+
node_scope = (
|
|
123
|
+
self.container.scope_factory.for_node(
|
|
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
|
+
)
|
|
130
|
+
if self.container.scope_factory
|
|
131
|
+
else None
|
|
132
|
+
)
|
|
133
|
+
|
|
121
134
|
level, custom_scope_id = self._resolve_memory_config()
|
|
122
135
|
mem_scope = (
|
|
123
136
|
self.container.scope_factory.for_memory(
|
|
@@ -133,24 +146,25 @@ class RuntimeEnv:
|
|
|
133
146
|
else None
|
|
134
147
|
)
|
|
135
148
|
|
|
149
|
+
indices: ScopedIndices | None = None # scoped indices for this node
|
|
150
|
+
if self.container.global_indices is not None and node_scope is not None:
|
|
151
|
+
# Attach scoped indices to container for this node's scope
|
|
152
|
+
# Prefer memory scope id if available for memory-tied corpora
|
|
153
|
+
base_scope = mem_scope or node_scope
|
|
154
|
+
if base_scope:
|
|
155
|
+
scope_id = mem_scope.memory_scope_id() if mem_scope else None
|
|
156
|
+
indices = self.container.global_indices.for_scope(
|
|
157
|
+
scope=base_scope,
|
|
158
|
+
scope_id=scope_id,
|
|
159
|
+
)
|
|
160
|
+
|
|
136
161
|
mem: MemoryFacade = self.memory_factory.for_session(
|
|
137
162
|
run_id=self.run_id,
|
|
138
163
|
graph_id=self.graph_id,
|
|
139
164
|
node_id=node.node_id,
|
|
140
165
|
session_id=self.session_id,
|
|
141
166
|
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
|
|
167
|
+
scoped_indices=indices,
|
|
154
168
|
)
|
|
155
169
|
|
|
156
170
|
from aethergraph.services.artifacts.facade import ArtifactFacade
|
|
@@ -161,8 +175,9 @@ class RuntimeEnv:
|
|
|
161
175
|
node_id=node.node_id,
|
|
162
176
|
tool_name=node.tool_name,
|
|
163
177
|
tool_version=node.tool_version, # to be filled from node if available
|
|
164
|
-
|
|
165
|
-
|
|
178
|
+
art_store=self.artifacts,
|
|
179
|
+
art_index=self.artifact_index,
|
|
180
|
+
scoped_indices=indices,
|
|
166
181
|
scope=node_scope,
|
|
167
182
|
)
|
|
168
183
|
|
|
@@ -202,6 +217,12 @@ class RuntimeEnv:
|
|
|
202
217
|
rag=rag_for_node, # RAGService
|
|
203
218
|
mcp=self.mcp_service, # MCPService
|
|
204
219
|
run_manager=self.container.run_manager, # RunManager
|
|
220
|
+
indices=indices, # ScopedIndices for this node
|
|
221
|
+
execution=self.container.execution
|
|
222
|
+
if self.container.execution is not None
|
|
223
|
+
else None, # ExecutionService
|
|
224
|
+
planner_service=self.container.planner_service,
|
|
225
|
+
skills=self.container.skills_registry,
|
|
205
226
|
)
|
|
206
227
|
return ExecutionContext(
|
|
207
228
|
run_id=self.run_id,
|
|
@@ -2,11 +2,14 @@ from collections.abc import Callable
|
|
|
2
2
|
from contextlib import contextmanager
|
|
3
3
|
from contextvars import ContextVar
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from threading import RLock
|
|
5
6
|
from typing import Any
|
|
6
7
|
|
|
7
8
|
from aethergraph.contracts.services.llm import LLMClientProtocol
|
|
8
9
|
from aethergraph.core.runtime.base_service import Service
|
|
9
10
|
from aethergraph.services.llm.generic_client import GenericLLMClient
|
|
11
|
+
from aethergraph.services.skills.skill_registry import SkillRegistry
|
|
12
|
+
from aethergraph.services.skills.skills import Skill
|
|
10
13
|
|
|
11
14
|
_current = ContextVar("aeg_services", default=None)
|
|
12
15
|
# process-wide fallback (handles contextvar boundary issues)
|
|
@@ -15,18 +18,72 @@ _services_global: Any = None
|
|
|
15
18
|
_pending_ext_services: dict[str, Any] = {}
|
|
16
19
|
|
|
17
20
|
|
|
21
|
+
_pending_lock = RLock()
|
|
22
|
+
|
|
23
|
+
# Ordered operations (some things depend on earlier steps)
|
|
24
|
+
_pending_ops_order: list[str] = []
|
|
25
|
+
# Keyed storage so repeated registrations overwrite instead of duplicating
|
|
26
|
+
_pending_ops: dict[str, Callable[[Any], Any]] = {}
|
|
27
|
+
# Optional: store results if you want “handles” later
|
|
28
|
+
_pending_results: dict[str, Any] = {}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _defer_op(key: str, op: Callable[[Any], Any]) -> None:
|
|
32
|
+
"""Register (or replace) a deferred operation."""
|
|
33
|
+
with _pending_lock:
|
|
34
|
+
if key not in _pending_ops:
|
|
35
|
+
_pending_ops_order.append(key)
|
|
36
|
+
_pending_ops[key] = op
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _flush_pending_ops(services: Any) -> None:
|
|
40
|
+
"""Apply all deferred operations once services exist."""
|
|
41
|
+
with _pending_lock:
|
|
42
|
+
keys = list(_pending_ops_order)
|
|
43
|
+
_pending_ops_order.clear()
|
|
44
|
+
ops = _pending_ops.copy()
|
|
45
|
+
_pending_ops.clear()
|
|
46
|
+
|
|
47
|
+
for key in keys:
|
|
48
|
+
op = ops.get(key)
|
|
49
|
+
if op is None:
|
|
50
|
+
continue
|
|
51
|
+
try:
|
|
52
|
+
_pending_results[key] = op(services)
|
|
53
|
+
except Exception:
|
|
54
|
+
# You can choose to log here instead of raising,
|
|
55
|
+
# but raising is usually better so startup fails loudly.
|
|
56
|
+
raise
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _try_apply_or_defer(key: str, fn: Callable[[Any], Any]) -> Any | None:
|
|
60
|
+
"""
|
|
61
|
+
If services installed: run now and return result.
|
|
62
|
+
Else: defer it and return None.
|
|
63
|
+
"""
|
|
64
|
+
try:
|
|
65
|
+
svc = current_services()
|
|
66
|
+
except RuntimeError:
|
|
67
|
+
_defer_op(key, fn)
|
|
68
|
+
return None
|
|
69
|
+
else:
|
|
70
|
+
return fn(svc)
|
|
71
|
+
|
|
72
|
+
|
|
18
73
|
def install_services(services: Any) -> None:
|
|
19
74
|
global _services_global, _pending_ext_services
|
|
20
75
|
_services_global = services
|
|
21
76
|
|
|
22
|
-
# Attach
|
|
77
|
+
# Attach pending ext services (your existing behavior)
|
|
23
78
|
ext = getattr(services, "ext_services", None)
|
|
24
79
|
if isinstance(ext, dict) and _pending_ext_services:
|
|
25
|
-
# Don't clobber anything that was already present.
|
|
26
80
|
for name, svc in _pending_ext_services.items():
|
|
27
81
|
ext.setdefault(name, svc)
|
|
28
82
|
_pending_ext_services = {}
|
|
29
83
|
|
|
84
|
+
# NEW: apply all other pending mutations
|
|
85
|
+
_flush_pending_ops(services)
|
|
86
|
+
|
|
30
87
|
return _current.set(services)
|
|
31
88
|
|
|
32
89
|
|
|
@@ -37,12 +94,16 @@ def ensure_services_installed(factory: Callable[[], Any]) -> Any:
|
|
|
37
94
|
svc = factory()
|
|
38
95
|
_services_global = svc
|
|
39
96
|
|
|
40
|
-
# hydrate pending external services
|
|
97
|
+
# hydrate pending external services
|
|
41
98
|
ext = getattr(svc, "ext_services", None)
|
|
42
99
|
if isinstance(ext, dict) and _pending_ext_services:
|
|
43
100
|
for name, s in _pending_ext_services.items():
|
|
44
101
|
ext.setdefault(name, s)
|
|
45
102
|
_pending_ext_services = {}
|
|
103
|
+
|
|
104
|
+
# NEW: apply pending ops on first creation too
|
|
105
|
+
_flush_pending_ops(svc)
|
|
106
|
+
|
|
46
107
|
_current.set(svc)
|
|
47
108
|
return svc
|
|
48
109
|
|
|
@@ -72,9 +133,10 @@ def get_channel_service() -> Any:
|
|
|
72
133
|
|
|
73
134
|
|
|
74
135
|
def set_default_channel(key: str) -> None:
|
|
75
|
-
svc
|
|
76
|
-
|
|
77
|
-
|
|
136
|
+
def _op(svc: Any) -> None:
|
|
137
|
+
svc.channels.set_default_channel_key(key)
|
|
138
|
+
|
|
139
|
+
return _try_apply_or_defer(key, _op)
|
|
78
140
|
|
|
79
141
|
|
|
80
142
|
def get_default_channel() -> str:
|
|
@@ -89,7 +151,7 @@ def set_channel_alias(alias: str, channel_key: str) -> None:
|
|
|
89
151
|
|
|
90
152
|
def register_channel_adapter(name: str, adapter: Any) -> None:
|
|
91
153
|
svc = current_services()
|
|
92
|
-
svc.
|
|
154
|
+
svc.channels.register_adapter(name, adapter)
|
|
93
155
|
|
|
94
156
|
|
|
95
157
|
# --------- LLM service helpers ---------
|
|
@@ -107,17 +169,20 @@ def register_llm_client(
|
|
|
107
169
|
api_key: str | None = None,
|
|
108
170
|
timeout: float | None = None,
|
|
109
171
|
) -> None:
|
|
110
|
-
svc
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
172
|
+
def _op(svc: Any) -> LLMClientProtocol:
|
|
173
|
+
client = svc.llm.configure_profile(
|
|
174
|
+
profile=profile,
|
|
175
|
+
provider=provider,
|
|
176
|
+
model=model,
|
|
177
|
+
embed_model=embed_model,
|
|
178
|
+
base_url=base_url,
|
|
179
|
+
api_key=api_key,
|
|
180
|
+
timeout=timeout,
|
|
181
|
+
)
|
|
182
|
+
return client
|
|
183
|
+
|
|
184
|
+
key = f"llm_client:profile={profile}:provider={provider}:model={model}"
|
|
185
|
+
return _try_apply_or_defer(key, _op)
|
|
121
186
|
|
|
122
187
|
|
|
123
188
|
# backend compatibility
|
|
@@ -402,6 +467,231 @@ def list_mcp_clients() -> list[str]:
|
|
|
402
467
|
return []
|
|
403
468
|
|
|
404
469
|
|
|
470
|
+
# --------- Skill registry helpers ---------
|
|
471
|
+
def get_skill_registry() -> SkillRegistry:
|
|
472
|
+
svc = current_services()
|
|
473
|
+
return svc.skills_registry
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def register_skill(skill: Skill, *, overwrite: bool = False) -> Skill:
|
|
477
|
+
"""
|
|
478
|
+
Register an existing Skill object into the global registry.
|
|
479
|
+
|
|
480
|
+
This method adds a `Skill` instance to the global `SkillRegistry`, making it
|
|
481
|
+
available for use throughout the application. The `overwrite` flag determines
|
|
482
|
+
whether an existing skill with the same ID will be replaced.
|
|
483
|
+
|
|
484
|
+
Examples:
|
|
485
|
+
Registering a skill object:
|
|
486
|
+
```python
|
|
487
|
+
skill = Skill(id="example.skill", title="Example Skill")
|
|
488
|
+
register_skill(skill)
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
Overwriting an existing skill:
|
|
492
|
+
```python
|
|
493
|
+
skill = Skill(id="example.skill", title="Updated Skill")
|
|
494
|
+
register_skill(skill, overwrite=True)
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
skill: The `Skill` object to register.
|
|
499
|
+
overwrite: Whether to overwrite an existing skill with the same ID. Default is `False`.
|
|
500
|
+
|
|
501
|
+
Returns:
|
|
502
|
+
Skill: The registered `Skill` instance.
|
|
503
|
+
|
|
504
|
+
"""
|
|
505
|
+
|
|
506
|
+
def _op(svc: Any) -> "Skill":
|
|
507
|
+
reg = svc.skills_registry
|
|
508
|
+
reg.register(skill, overwrite=overwrite)
|
|
509
|
+
return skill
|
|
510
|
+
|
|
511
|
+
# Key should be stable and allow overwriting the deferred op if called again.
|
|
512
|
+
# Usually skill.id is the right identity here.
|
|
513
|
+
key = f"skills:obj:{skill.id}:overwrite={overwrite}"
|
|
514
|
+
return _try_apply_or_defer(key, _op)
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
def register_skill_inline(
|
|
518
|
+
*,
|
|
519
|
+
id: str,
|
|
520
|
+
title: str,
|
|
521
|
+
description: str = "",
|
|
522
|
+
tags: list[str] | None = None,
|
|
523
|
+
domain: str | None = None,
|
|
524
|
+
modes: list[str] | None = None,
|
|
525
|
+
version: str | None = None,
|
|
526
|
+
config: dict[str, Any] | None = None,
|
|
527
|
+
sections: dict[str, str] | None = None,
|
|
528
|
+
overwrite: bool = False,
|
|
529
|
+
) -> Skill:
|
|
530
|
+
"""
|
|
531
|
+
Define and register a Skill entirely in Python.
|
|
532
|
+
|
|
533
|
+
This method allows you to define a Skill inline with all its metadata and sections,
|
|
534
|
+
and directly register it into the global Skill registry.
|
|
535
|
+
|
|
536
|
+
Examples:
|
|
537
|
+
Registering a skill with basic metadata and sections:
|
|
538
|
+
```python
|
|
539
|
+
register_skill_inline(
|
|
540
|
+
id="surrogate.workflow",
|
|
541
|
+
title="Surrogate workflow planning",
|
|
542
|
+
description="Prompts and patterns for surrogate planning.",
|
|
543
|
+
tags=["surrogate", "planning"],
|
|
544
|
+
modes=["planning"],
|
|
545
|
+
sections={
|
|
546
|
+
"planning.header": "...",
|
|
547
|
+
"planning.binding_hints": "...",
|
|
548
|
+
"chat.system": "...",
|
|
549
|
+
},
|
|
550
|
+
)
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
Args:
|
|
554
|
+
id (str): The unique identifier for the Skill. (Required)
|
|
555
|
+
title (str): A human-readable title for the Skill. (Required)
|
|
556
|
+
description (str): A short description of the Skill's purpose. (Optional)
|
|
557
|
+
tags (list[str]): A list of tags for categorization. (Optional)
|
|
558
|
+
domain (str): The domain or namespace for the Skill. (Optional)
|
|
559
|
+
modes (list[str]): The operational modes supported by the Skill. (Optional)
|
|
560
|
+
version (str): The version string for the Skill. (Optional)
|
|
561
|
+
config (dict[str, Any]): Additional configuration data. (Optional)
|
|
562
|
+
sections (dict[str, str]): A dictionary mapping section names to their content. (Optional)
|
|
563
|
+
overwrite (bool): Whether to overwrite an existing Skill with the same ID. (Optional)
|
|
564
|
+
|
|
565
|
+
Returns:
|
|
566
|
+
Skill: The registered Skill instance.
|
|
567
|
+
"""
|
|
568
|
+
|
|
569
|
+
def _op(svc: Any) -> "Skill":
|
|
570
|
+
reg = svc.skills_registry
|
|
571
|
+
return reg.register_inline(
|
|
572
|
+
id=id,
|
|
573
|
+
title=title,
|
|
574
|
+
description=description,
|
|
575
|
+
tags=tags,
|
|
576
|
+
domain=domain,
|
|
577
|
+
modes=modes,
|
|
578
|
+
version=version,
|
|
579
|
+
config=config,
|
|
580
|
+
sections=sections,
|
|
581
|
+
overwrite=overwrite,
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
# Include overwrite, and optionally version to avoid surprising replacements.
|
|
585
|
+
key = f"skills:inline:{id}:overwrite={overwrite}:version={version or ''}"
|
|
586
|
+
return _try_apply_or_defer(key, _op)
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
def register_skill_file(path: str | Path, *, overwrite: bool = False) -> Skill:
|
|
590
|
+
"""
|
|
591
|
+
Load a single markdown skill file and register it.
|
|
592
|
+
|
|
593
|
+
This function processes a markdown file containing skill definitions and
|
|
594
|
+
registers it into the global skill registry. The file must adhere to the
|
|
595
|
+
expected format for parsing skill metadata and sections.
|
|
596
|
+
|
|
597
|
+
Examples:
|
|
598
|
+
Registering a skill from a markdown file:
|
|
599
|
+
```python
|
|
600
|
+
skill = register_skill_file("skills/surrogate-workflow.md")
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
Args:
|
|
604
|
+
path: The path to the markdown file to load.
|
|
605
|
+
overwrite: Whether to overwrite an existing skill with the same ID. (Optional, default: False)
|
|
606
|
+
|
|
607
|
+
Returns:
|
|
608
|
+
Skill: The registered `Skill` instance.
|
|
609
|
+
|
|
610
|
+
Notes:
|
|
611
|
+
To start the server and load all desired packages:
|
|
612
|
+
1. Open a terminal and navigate to the project directory.
|
|
613
|
+
2. Run the server using the appropriate command (e.g., `python -m aethergraph.server`).
|
|
614
|
+
3. Ensure all required dependencies are installed via `pip install -r requirements.txt`.
|
|
615
|
+
|
|
616
|
+
"""
|
|
617
|
+
|
|
618
|
+
p = str(path)
|
|
619
|
+
|
|
620
|
+
def _op(svc: Any) -> Skill:
|
|
621
|
+
reg = svc.skills_registry
|
|
622
|
+
return reg.load_file(path, overwrite=overwrite)
|
|
623
|
+
|
|
624
|
+
p = str(path)
|
|
625
|
+
|
|
626
|
+
def _op(svc: Any) -> "Skill":
|
|
627
|
+
reg = svc.skills_registry
|
|
628
|
+
return reg.load_file(path, overwrite=overwrite)
|
|
629
|
+
|
|
630
|
+
key = f"skills:file:{p}:overwrite={overwrite}"
|
|
631
|
+
return _try_apply_or_defer(key, _op)
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
def register_skills_from_path(
|
|
635
|
+
root: str | Path,
|
|
636
|
+
*,
|
|
637
|
+
pattern: str = "*.md",
|
|
638
|
+
recursive: bool = True,
|
|
639
|
+
overwrite: bool = False,
|
|
640
|
+
) -> list[Skill]:
|
|
641
|
+
"""
|
|
642
|
+
Load and register all skill markdown files under a directory.
|
|
643
|
+
|
|
644
|
+
This method scans the specified directory for markdown files matching the
|
|
645
|
+
given pattern, parses their content into `Skill` objects, and registers
|
|
646
|
+
them into the global skill registry. The directory can have a flat or
|
|
647
|
+
nested structure.
|
|
648
|
+
|
|
649
|
+
Examples:
|
|
650
|
+
Register all skills in a flat directory:
|
|
651
|
+
```python
|
|
652
|
+
register_skills_from_path("skills/")
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
Register skills in a nested directory structure:
|
|
656
|
+
```python
|
|
657
|
+
register_skills_from_path("skills/", recursive=True)
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
Use a custom file pattern to filter files:
|
|
661
|
+
```python
|
|
662
|
+
register_skills_from_path("skills/", pattern="*.skill.md")
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
Args:
|
|
666
|
+
root: The root directory to scan for skill files.
|
|
667
|
+
pattern: A glob pattern to match skill files. Default is `"*.md"`.
|
|
668
|
+
recursive: Whether to scan subdirectories recursively. Default is `True`.
|
|
669
|
+
overwrite: Whether to overwrite existing skills with the same ID. Default is `False`.
|
|
670
|
+
|
|
671
|
+
Returns:
|
|
672
|
+
list[Skill]: A list of all registered `Skill` objects.
|
|
673
|
+
|
|
674
|
+
Notes:
|
|
675
|
+
To start the server and load all desired packages:
|
|
676
|
+
1. Open a terminal and navigate to the project directory.
|
|
677
|
+
2. Run the server using the appropriate command (e.g., `python -m aethergraph.server`).
|
|
678
|
+
3. Ensure all required dependencies are installed via `pip install -r requirements.txt`.
|
|
679
|
+
|
|
680
|
+
"""
|
|
681
|
+
root_str = str(root)
|
|
682
|
+
|
|
683
|
+
def _op(svc: Any) -> list[Skill]:
|
|
684
|
+
return svc.skills_registry.load_path(
|
|
685
|
+
root=root_str,
|
|
686
|
+
pattern=pattern,
|
|
687
|
+
recursive=recursive,
|
|
688
|
+
overwrite=overwrite,
|
|
689
|
+
)
|
|
690
|
+
|
|
691
|
+
key = f"skills:path:{root_str}:pattern={pattern}:recursive={recursive}:overwrite={overwrite}"
|
|
692
|
+
return _try_apply_or_defer(key, _op)
|
|
693
|
+
|
|
694
|
+
|
|
405
695
|
# --------- Scheduler helpers --------- - (Not used)
|
|
406
696
|
def ensure_global_scheduler_started() -> None:
|
|
407
697
|
svc = current_services()
|