aethergraph 0.1.0a1__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.
Files changed (182) hide show
  1. aethergraph/__init__.py +49 -0
  2. aethergraph/config/__init__.py +0 -0
  3. aethergraph/config/config.py +121 -0
  4. aethergraph/config/context.py +16 -0
  5. aethergraph/config/llm.py +26 -0
  6. aethergraph/config/loader.py +60 -0
  7. aethergraph/config/runtime.py +9 -0
  8. aethergraph/contracts/errors/errors.py +44 -0
  9. aethergraph/contracts/services/artifacts.py +142 -0
  10. aethergraph/contracts/services/channel.py +72 -0
  11. aethergraph/contracts/services/continuations.py +23 -0
  12. aethergraph/contracts/services/eventbus.py +12 -0
  13. aethergraph/contracts/services/kv.py +24 -0
  14. aethergraph/contracts/services/llm.py +17 -0
  15. aethergraph/contracts/services/mcp.py +22 -0
  16. aethergraph/contracts/services/memory.py +108 -0
  17. aethergraph/contracts/services/resume.py +28 -0
  18. aethergraph/contracts/services/state_stores.py +33 -0
  19. aethergraph/contracts/services/wakeup.py +28 -0
  20. aethergraph/core/execution/base_scheduler.py +77 -0
  21. aethergraph/core/execution/forward_scheduler.py +777 -0
  22. aethergraph/core/execution/global_scheduler.py +634 -0
  23. aethergraph/core/execution/retry_policy.py +22 -0
  24. aethergraph/core/execution/step_forward.py +411 -0
  25. aethergraph/core/execution/step_result.py +18 -0
  26. aethergraph/core/execution/wait_types.py +72 -0
  27. aethergraph/core/graph/graph_builder.py +192 -0
  28. aethergraph/core/graph/graph_fn.py +219 -0
  29. aethergraph/core/graph/graph_io.py +67 -0
  30. aethergraph/core/graph/graph_refs.py +154 -0
  31. aethergraph/core/graph/graph_spec.py +115 -0
  32. aethergraph/core/graph/graph_state.py +59 -0
  33. aethergraph/core/graph/graphify.py +128 -0
  34. aethergraph/core/graph/interpreter.py +145 -0
  35. aethergraph/core/graph/node_handle.py +33 -0
  36. aethergraph/core/graph/node_spec.py +46 -0
  37. aethergraph/core/graph/node_state.py +63 -0
  38. aethergraph/core/graph/task_graph.py +747 -0
  39. aethergraph/core/graph/task_node.py +82 -0
  40. aethergraph/core/graph/utils.py +37 -0
  41. aethergraph/core/graph/visualize.py +239 -0
  42. aethergraph/core/runtime/ad_hoc_context.py +61 -0
  43. aethergraph/core/runtime/base_service.py +153 -0
  44. aethergraph/core/runtime/bind_adapter.py +42 -0
  45. aethergraph/core/runtime/bound_memory.py +69 -0
  46. aethergraph/core/runtime/execution_context.py +220 -0
  47. aethergraph/core/runtime/graph_runner.py +349 -0
  48. aethergraph/core/runtime/lifecycle.py +26 -0
  49. aethergraph/core/runtime/node_context.py +203 -0
  50. aethergraph/core/runtime/node_services.py +30 -0
  51. aethergraph/core/runtime/recovery.py +159 -0
  52. aethergraph/core/runtime/run_registration.py +33 -0
  53. aethergraph/core/runtime/runtime_env.py +157 -0
  54. aethergraph/core/runtime/runtime_registry.py +32 -0
  55. aethergraph/core/runtime/runtime_services.py +224 -0
  56. aethergraph/core/runtime/wakeup_watcher.py +40 -0
  57. aethergraph/core/tools/__init__.py +10 -0
  58. aethergraph/core/tools/builtins/channel_tools.py +194 -0
  59. aethergraph/core/tools/builtins/toolset.py +134 -0
  60. aethergraph/core/tools/toolkit.py +510 -0
  61. aethergraph/core/tools/waitable.py +109 -0
  62. aethergraph/plugins/channel/__init__.py +0 -0
  63. aethergraph/plugins/channel/adapters/__init__.py +0 -0
  64. aethergraph/plugins/channel/adapters/console.py +106 -0
  65. aethergraph/plugins/channel/adapters/file.py +102 -0
  66. aethergraph/plugins/channel/adapters/slack.py +285 -0
  67. aethergraph/plugins/channel/adapters/telegram.py +302 -0
  68. aethergraph/plugins/channel/adapters/webhook.py +104 -0
  69. aethergraph/plugins/channel/adapters/webui.py +134 -0
  70. aethergraph/plugins/channel/routes/__init__.py +0 -0
  71. aethergraph/plugins/channel/routes/console_routes.py +86 -0
  72. aethergraph/plugins/channel/routes/slack_routes.py +49 -0
  73. aethergraph/plugins/channel/routes/telegram_routes.py +26 -0
  74. aethergraph/plugins/channel/routes/webui_routes.py +136 -0
  75. aethergraph/plugins/channel/utils/__init__.py +0 -0
  76. aethergraph/plugins/channel/utils/slack_utils.py +278 -0
  77. aethergraph/plugins/channel/utils/telegram_utils.py +324 -0
  78. aethergraph/plugins/channel/websockets/slack_ws.py +68 -0
  79. aethergraph/plugins/channel/websockets/telegram_polling.py +151 -0
  80. aethergraph/plugins/mcp/fs_server.py +128 -0
  81. aethergraph/plugins/mcp/http_server.py +101 -0
  82. aethergraph/plugins/mcp/ws_server.py +180 -0
  83. aethergraph/plugins/net/http.py +10 -0
  84. aethergraph/plugins/utils/data_io.py +359 -0
  85. aethergraph/runner/__init__.py +5 -0
  86. aethergraph/runtime/__init__.py +62 -0
  87. aethergraph/server/__init__.py +3 -0
  88. aethergraph/server/app_factory.py +84 -0
  89. aethergraph/server/start.py +122 -0
  90. aethergraph/services/__init__.py +10 -0
  91. aethergraph/services/artifacts/facade.py +284 -0
  92. aethergraph/services/artifacts/factory.py +35 -0
  93. aethergraph/services/artifacts/fs_store.py +656 -0
  94. aethergraph/services/artifacts/jsonl_index.py +123 -0
  95. aethergraph/services/artifacts/paths.py +23 -0
  96. aethergraph/services/artifacts/sqlite_index.py +209 -0
  97. aethergraph/services/artifacts/utils.py +124 -0
  98. aethergraph/services/auth/dev.py +16 -0
  99. aethergraph/services/channel/channel_bus.py +293 -0
  100. aethergraph/services/channel/factory.py +44 -0
  101. aethergraph/services/channel/session.py +511 -0
  102. aethergraph/services/channel/wait_helpers.py +57 -0
  103. aethergraph/services/clock/clock.py +9 -0
  104. aethergraph/services/container/default_container.py +320 -0
  105. aethergraph/services/continuations/continuation.py +56 -0
  106. aethergraph/services/continuations/factory.py +34 -0
  107. aethergraph/services/continuations/stores/fs_store.py +264 -0
  108. aethergraph/services/continuations/stores/inmem_store.py +95 -0
  109. aethergraph/services/eventbus/inmem.py +21 -0
  110. aethergraph/services/features/static.py +10 -0
  111. aethergraph/services/kv/ephemeral.py +90 -0
  112. aethergraph/services/kv/factory.py +27 -0
  113. aethergraph/services/kv/layered.py +41 -0
  114. aethergraph/services/kv/sqlite_kv.py +128 -0
  115. aethergraph/services/llm/factory.py +157 -0
  116. aethergraph/services/llm/generic_client.py +542 -0
  117. aethergraph/services/llm/providers.py +3 -0
  118. aethergraph/services/llm/service.py +105 -0
  119. aethergraph/services/logger/base.py +36 -0
  120. aethergraph/services/logger/compat.py +50 -0
  121. aethergraph/services/logger/formatters.py +106 -0
  122. aethergraph/services/logger/std.py +203 -0
  123. aethergraph/services/mcp/helpers.py +23 -0
  124. aethergraph/services/mcp/http_client.py +70 -0
  125. aethergraph/services/mcp/mcp_tools.py +21 -0
  126. aethergraph/services/mcp/registry.py +14 -0
  127. aethergraph/services/mcp/service.py +100 -0
  128. aethergraph/services/mcp/stdio_client.py +70 -0
  129. aethergraph/services/mcp/ws_client.py +115 -0
  130. aethergraph/services/memory/bound.py +106 -0
  131. aethergraph/services/memory/distillers/episode.py +116 -0
  132. aethergraph/services/memory/distillers/rolling.py +74 -0
  133. aethergraph/services/memory/facade.py +633 -0
  134. aethergraph/services/memory/factory.py +78 -0
  135. aethergraph/services/memory/hotlog_kv.py +27 -0
  136. aethergraph/services/memory/indices.py +74 -0
  137. aethergraph/services/memory/io_helpers.py +72 -0
  138. aethergraph/services/memory/persist_fs.py +40 -0
  139. aethergraph/services/memory/resolver.py +152 -0
  140. aethergraph/services/metering/noop.py +4 -0
  141. aethergraph/services/prompts/file_store.py +41 -0
  142. aethergraph/services/rag/chunker.py +29 -0
  143. aethergraph/services/rag/facade.py +593 -0
  144. aethergraph/services/rag/index/base.py +27 -0
  145. aethergraph/services/rag/index/faiss_index.py +121 -0
  146. aethergraph/services/rag/index/sqlite_index.py +134 -0
  147. aethergraph/services/rag/index_factory.py +52 -0
  148. aethergraph/services/rag/parsers/md.py +7 -0
  149. aethergraph/services/rag/parsers/pdf.py +14 -0
  150. aethergraph/services/rag/parsers/txt.py +7 -0
  151. aethergraph/services/rag/utils/hybrid.py +39 -0
  152. aethergraph/services/rag/utils/make_fs_key.py +62 -0
  153. aethergraph/services/redactor/simple.py +16 -0
  154. aethergraph/services/registry/key_parsing.py +44 -0
  155. aethergraph/services/registry/registry_key.py +19 -0
  156. aethergraph/services/registry/unified_registry.py +185 -0
  157. aethergraph/services/resume/multi_scheduler_resume_bus.py +65 -0
  158. aethergraph/services/resume/router.py +73 -0
  159. aethergraph/services/schedulers/registry.py +41 -0
  160. aethergraph/services/secrets/base.py +7 -0
  161. aethergraph/services/secrets/env.py +8 -0
  162. aethergraph/services/state_stores/externalize.py +135 -0
  163. aethergraph/services/state_stores/graph_observer.py +131 -0
  164. aethergraph/services/state_stores/json_store.py +67 -0
  165. aethergraph/services/state_stores/resume_policy.py +119 -0
  166. aethergraph/services/state_stores/serialize.py +249 -0
  167. aethergraph/services/state_stores/utils.py +91 -0
  168. aethergraph/services/state_stores/validate.py +78 -0
  169. aethergraph/services/tracing/noop.py +18 -0
  170. aethergraph/services/waits/wait_registry.py +91 -0
  171. aethergraph/services/wakeup/memory_queue.py +57 -0
  172. aethergraph/services/wakeup/scanner_producer.py +56 -0
  173. aethergraph/services/wakeup/worker.py +31 -0
  174. aethergraph/tools/__init__.py +25 -0
  175. aethergraph/utils/optdeps.py +8 -0
  176. aethergraph-0.1.0a1.dist-info/METADATA +410 -0
  177. aethergraph-0.1.0a1.dist-info/RECORD +182 -0
  178. aethergraph-0.1.0a1.dist-info/WHEEL +5 -0
  179. aethergraph-0.1.0a1.dist-info/entry_points.txt +2 -0
  180. aethergraph-0.1.0a1.dist-info/licenses/LICENSE +176 -0
  181. aethergraph-0.1.0a1.dist-info/licenses/NOTICE +31 -0
  182. aethergraph-0.1.0a1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,108 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Literal, Protocol, TypedDict
5
+
6
+ EventKind = Literal[
7
+ "user_msg",
8
+ "assistant_msg",
9
+ "tool_start",
10
+ "tool_result",
11
+ "error",
12
+ "checkpoint",
13
+ "run_summary",
14
+ "rolling_summary",
15
+ ]
16
+
17
+
18
+ @dataclass
19
+ class Event:
20
+ event_id: str
21
+ ts: str
22
+ run_id: str
23
+ graph_id: str | None = None
24
+ node_id: str | None = None
25
+ agent_id: str | None = None
26
+ tool: str | None = None # now used for tool topic: TODO: rename to topic in future
27
+ kind: EventKind = "tool_result"
28
+ stage: str | None = None
29
+ severity: int = 2
30
+ signal: float = 0.0
31
+ tags: list[str] | None = None
32
+ entities: list[str] | None = None
33
+ inputs: list[Value] | None = None
34
+ outputs: list[Value] | None = None
35
+ inputs_ref: dict[str, Any] | None = None
36
+ outputs_ref: dict[str, Any] | None = None
37
+ metrics: dict[str, float] | None = None
38
+ text: str | None = None
39
+ embedding: list[float] | None = None
40
+ pii_flags: dict[str, bool] | None = None
41
+ sources: list[str] | None = None
42
+ version: int = 1 # for schema evolution
43
+
44
+
45
+ class HotLog(Protocol):
46
+ async def append(self, run_id: str, evt: Event, *, ttl_s: int, limit: int) -> None: ...
47
+ async def recent(
48
+ self, run_id: str, *, kinds: list[str] | None = None, limit: int = 50
49
+ ) -> list[Event]: ...
50
+
51
+
52
+ class Persistence(Protocol):
53
+ async def append_event(self, run_id: str, evt: Event) -> None: ...
54
+ async def save_json(self, uri: str, obj: dict[str, Any]) -> None: ...
55
+
56
+
57
+ class Indices(Protocol):
58
+ async def update(self, run_id: str, evt: Event) -> None: ...
59
+ async def last_by_name(self, run_id: str, name: str) -> dict[str, Any] | None: ...
60
+ async def latest_refs_by_kind(
61
+ self, run_id: str, kind: str, *, limit: int = 50
62
+ ) -> list[dict[str, Any]]: ...
63
+ async def last_outputs_by_topic(self, run_id: str, topic: str) -> dict[str, Any] | None: ...
64
+
65
+
66
+ class Distiller(Protocol):
67
+ async def distill(
68
+ self, run_id: str, *, hotlog: HotLog, persistence: Persistence, indices: Indices, **kw
69
+ ) -> dict[str, Any]: ...
70
+
71
+
72
+ # ---------- Vector Index and Embeddings Client Protocols ----------
73
+ class VectorIndex(Protocol):
74
+ async def upsert(self, *, id: str, vector: list[float], metadata: dict) -> None: ...
75
+ async def delete(self, *, id: str) -> None: ...
76
+ async def query(
77
+ self, *, vector: list[float], k: int = 8, filter: dict | None = None
78
+ ) -> list[dict]: ...
79
+ async def flush(self) -> None: ...
80
+
81
+
82
+ class EmbeddingsClient(Protocol):
83
+ async def embed_text(self, text: str, *, model: str | None = None) -> list[float]: ...
84
+ async def embed_texts(
85
+ self, texts: list[str], *, model: str | None = None
86
+ ) -> list[list[float]]: ...
87
+
88
+
89
+ # ---------- I/O Value and Ref schemas ----------
90
+ class Ref(TypedDict, total=False):
91
+ """A resolvable refernece to an external artifact or data."""
92
+
93
+ kind: str # e.g. "spec", "design", "output", "tool_result"
94
+ uri: str # e.g. "file://...", "mem://...", "db://..."
95
+ title: str | None # optional human-readable title
96
+ mime: str | None # optional MIME type, e.g. "image/png"
97
+
98
+
99
+ class Value(TypedDict, total=False):
100
+ """
101
+ A named I/O slot that can hold any JSON-serializable value, including a Ref.
102
+ vtype declares the JSON type; if vtype == "ref", value must be a Ref dict.
103
+ """
104
+
105
+ name: str
106
+ vtype: Literal["ref", "number", "string", "boolean", "object", "array", "null"]
107
+ value: Any # actual value; type depends on vtype
108
+ meta: dict[str, Any] | None # optional metadata dictionary
@@ -0,0 +1,28 @@
1
+ from abc import abstractmethod
2
+ from dataclasses import dataclass
3
+ from typing import Protocol
4
+
5
+
6
+ class ResumeBus(Protocol):
7
+ """
8
+ Abstract transport for resuming a waiting node.
9
+ Implementations may be:
10
+ - InProcessResumeBus: directly calls the in-memory scheduler
11
+ - HttpResumeBus: POSTs to a remote scheduler service (not shown here)
12
+ """
13
+
14
+ @abstractmethod
15
+ async def enqueue_resume(self, *, run_id: str, node_id: str, token: str, payload: dict) -> None:
16
+ """
17
+ Verify the continuation/token (or let the backend do it), then
18
+ trigger a resume for (run_id, node_id) with the given payload.
19
+ Should be idempotent and safe to call multiple times.
20
+ """
21
+ raise NotImplementedError
22
+
23
+
24
+ @dataclass
25
+ class ResumeEvent:
26
+ run_id: str
27
+ node_id: str
28
+ payload: dict
@@ -0,0 +1,33 @@
1
+ # aethergraph/persist/interfaces.py
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass
5
+ from typing import Any, Protocol
6
+
7
+
8
+ @dataclass
9
+ class GraphSnapshot:
10
+ run_id: str
11
+ graph_id: str
12
+ rev: int
13
+ created_at: float # epoch seconds
14
+ spec_hash: str # detect spec drift
15
+ state: dict[str, Any] # JSON-serializable TaskGraphState
16
+
17
+
18
+ @dataclass
19
+ class StateEvent:
20
+ run_id: str
21
+ graph_id: str
22
+ rev: int
23
+ ts: float
24
+ kind: str # "STATUS" | "OUTPUT" | "INPUTS_BOUND" | "PATCH"
25
+ payload: dict[str, Any]
26
+
27
+
28
+ class GraphStateStore(Protocol):
29
+ async def save_snapshot(self, snap: GraphSnapshot) -> None: ...
30
+ async def load_latest_snapshot(self, run_id: str) -> GraphSnapshot | None: ...
31
+ async def append_event(self, ev: StateEvent) -> None: ...
32
+ async def load_events_since(self, run_id: str, from_rev: int) -> list[StateEvent]: ...
33
+ async def list_run_ids(self, graph_id: str | None = None) -> list[str]: ...
@@ -0,0 +1,28 @@
1
+ # core/contracts/wakeup.py
2
+ from dataclasses import dataclass
3
+ from typing import Any, Protocol
4
+
5
+
6
+ class WakeLease(Protocol):
7
+ """Lease handle for a wakeup message."""
8
+
9
+ id: str
10
+ msg: dict[str, Any] # {run_id, node_id, token, payload, ...}
11
+ visibility_deadline: float
12
+
13
+
14
+ class WakeupQueue(Protocol):
15
+ """Protocol for a wakeup queue service."""
16
+
17
+ async def enqueue(self, topic: str, msg: dict[str, Any], delay_s: float = 0) -> str: ...
18
+ async def lease(self, topic: str, max_items: int = 1, lease_s: int = 60) -> list[WakeLease]: ...
19
+ async def extend(self, lease: WakeLease, lease_s: int) -> None: ...
20
+ async def ack(self, lease: WakeLease) -> None: ...
21
+ async def nack(
22
+ self, lease: WakeLease, requeue_delay_s: float = 5
23
+ ) -> None: ... # re-enqueue the message
24
+
25
+
26
+ @dataclass
27
+ class WakeupEvent:
28
+ node_id: str
@@ -0,0 +1,77 @@
1
+ import asyncio
2
+ from typing import Literal
3
+
4
+ from aethergraph.core.graph.task_node import TaskNodeRuntime
5
+
6
+ # from aethergraph.logging_config import logger
7
+
8
+
9
+ ExecutionMode = Literal["forward", "backward"]
10
+
11
+
12
+ class BaseScheduler:
13
+ def __init__(self, graph, mode: ExecutionMode):
14
+ self.graph = graph
15
+ self.mode = mode
16
+ self._pause_event = asyncio.Event()
17
+ self._pause_event.set()
18
+ self._terminated = False
19
+ self.running_tasks = {} # Dictionary to track currently running tasks
20
+
21
+ self._nodes_to_pause = [] # Nodes that are requested to be paused
22
+
23
+ @property
24
+ def status(self) -> str:
25
+ if self._terminated:
26
+ return "terminated"
27
+ if not self._pause_event.is_set():
28
+ return "paused"
29
+ if self.get_running_task_node_ids():
30
+ return "running"
31
+ return "idle"
32
+
33
+ def reset_status(self):
34
+ """
35
+ Reset the scheduler's status to idle.
36
+ """
37
+ self._terminated = False
38
+ self._pause_event.set()
39
+ self.running_tasks.clear()
40
+ # logger.info(f"🔄 Scheduler status reset for graph `{self.graph.id}`")
41
+
42
+ def set_mode(self, mode: ExecutionMode):
43
+ """
44
+ Set the execution mode for this scheduler.
45
+ """
46
+ if mode not in ["forward", "backward"]:
47
+ raise ValueError(f"❌ Invalid execution mode: {mode}")
48
+ self.mode = mode
49
+ # logger.info(f"🔄 Execution mode set to {self.mode}")
50
+
51
+ def get_running_task_node_ids(self) -> list[str]:
52
+ """
53
+ Get a list of currently running task node IDs.
54
+ """
55
+ return [nid for nid, task in self.running_tasks.items() if not task.done()]
56
+
57
+ async def run(self):
58
+ raise NotImplementedError
59
+
60
+ async def run_from(self, node_ids: list[str]):
61
+ raise NotImplementedError("run_from() must be implemented in subclass")
62
+
63
+ async def pause(self):
64
+ self._pause_event.clear()
65
+
66
+ async def resume(self):
67
+ self._pause_event.set()
68
+
69
+ async def terminate(self):
70
+ self._terminated = True
71
+ raise NotImplementedError("terminate() must be implemented in subclass")
72
+
73
+ async def run_node(self, node: TaskNodeRuntime):
74
+ raise NotImplementedError("run_node() must be implemented in subclass")
75
+
76
+ async def step_next(self):
77
+ raise NotImplementedError("step_next() must be implemented in subclass")