aethergraph 0.1.0a3__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.
Files changed (113) hide show
  1. aethergraph/api/v1/artifacts.py +23 -4
  2. aethergraph/api/v1/schemas.py +7 -0
  3. aethergraph/api/v1/session.py +123 -4
  4. aethergraph/config/config.py +2 -0
  5. aethergraph/config/search.py +49 -0
  6. aethergraph/contracts/services/channel.py +18 -1
  7. aethergraph/contracts/services/execution.py +58 -0
  8. aethergraph/contracts/services/llm.py +26 -0
  9. aethergraph/contracts/services/memory.py +10 -4
  10. aethergraph/contracts/services/planning.py +53 -0
  11. aethergraph/contracts/storage/event_log.py +8 -0
  12. aethergraph/contracts/storage/search_backend.py +47 -0
  13. aethergraph/contracts/storage/vector_index.py +73 -0
  14. aethergraph/core/graph/action_spec.py +76 -0
  15. aethergraph/core/graph/graph_fn.py +75 -2
  16. aethergraph/core/graph/graphify.py +74 -2
  17. aethergraph/core/runtime/graph_runner.py +2 -1
  18. aethergraph/core/runtime/node_context.py +66 -3
  19. aethergraph/core/runtime/node_services.py +8 -0
  20. aethergraph/core/runtime/run_manager.py +263 -271
  21. aethergraph/core/runtime/run_types.py +54 -1
  22. aethergraph/core/runtime/runtime_env.py +35 -14
  23. aethergraph/core/runtime/runtime_services.py +308 -18
  24. aethergraph/plugins/agents/default_chat_agent.py +266 -74
  25. aethergraph/plugins/agents/default_chat_agent_v2.py +487 -0
  26. aethergraph/plugins/channel/adapters/webui.py +69 -21
  27. aethergraph/plugins/channel/routes/webui_routes.py +8 -48
  28. aethergraph/runtime/__init__.py +12 -0
  29. aethergraph/server/app_factory.py +3 -0
  30. aethergraph/server/ui_static/assets/index-CFktGdbW.js +4913 -0
  31. aethergraph/server/ui_static/assets/index-DcfkFlTA.css +1 -0
  32. aethergraph/server/ui_static/index.html +2 -2
  33. aethergraph/services/artifacts/facade.py +157 -21
  34. aethergraph/services/artifacts/types.py +35 -0
  35. aethergraph/services/artifacts/utils.py +42 -0
  36. aethergraph/services/channel/channel_bus.py +3 -1
  37. aethergraph/services/channel/event_hub copy.py +55 -0
  38. aethergraph/services/channel/event_hub.py +81 -0
  39. aethergraph/services/channel/factory.py +3 -2
  40. aethergraph/services/channel/session.py +709 -74
  41. aethergraph/services/container/default_container.py +69 -7
  42. aethergraph/services/execution/__init__.py +0 -0
  43. aethergraph/services/execution/local_python.py +118 -0
  44. aethergraph/services/indices/__init__.py +0 -0
  45. aethergraph/services/indices/global_indices.py +21 -0
  46. aethergraph/services/indices/scoped_indices.py +292 -0
  47. aethergraph/services/llm/generic_client.py +342 -46
  48. aethergraph/services/llm/generic_embed_client.py +359 -0
  49. aethergraph/services/llm/types.py +3 -1
  50. aethergraph/services/memory/distillers/llm_long_term.py +60 -109
  51. aethergraph/services/memory/distillers/llm_long_term_v1.py +180 -0
  52. aethergraph/services/memory/distillers/llm_meta_summary.py +57 -266
  53. aethergraph/services/memory/distillers/llm_meta_summary_v1.py +342 -0
  54. aethergraph/services/memory/distillers/long_term.py +48 -131
  55. aethergraph/services/memory/distillers/long_term_v1.py +170 -0
  56. aethergraph/services/memory/facade/chat.py +18 -8
  57. aethergraph/services/memory/facade/core.py +159 -19
  58. aethergraph/services/memory/facade/distillation.py +86 -31
  59. aethergraph/services/memory/facade/retrieval.py +100 -1
  60. aethergraph/services/memory/factory.py +4 -1
  61. aethergraph/services/planning/__init__.py +0 -0
  62. aethergraph/services/planning/action_catalog.py +271 -0
  63. aethergraph/services/planning/bindings.py +56 -0
  64. aethergraph/services/planning/dependency_index.py +65 -0
  65. aethergraph/services/planning/flow_validator.py +263 -0
  66. aethergraph/services/planning/graph_io_adapter.py +150 -0
  67. aethergraph/services/planning/input_parser.py +312 -0
  68. aethergraph/services/planning/missing_inputs.py +28 -0
  69. aethergraph/services/planning/node_planner.py +613 -0
  70. aethergraph/services/planning/orchestrator.py +112 -0
  71. aethergraph/services/planning/plan_executor.py +506 -0
  72. aethergraph/services/planning/plan_types.py +321 -0
  73. aethergraph/services/planning/planner.py +617 -0
  74. aethergraph/services/planning/planner_service.py +369 -0
  75. aethergraph/services/planning/planning_context_builder.py +43 -0
  76. aethergraph/services/planning/quick_actions.py +29 -0
  77. aethergraph/services/planning/routers/__init__.py +0 -0
  78. aethergraph/services/planning/routers/simple_router.py +26 -0
  79. aethergraph/services/rag/facade.py +0 -3
  80. aethergraph/services/scope/scope.py +30 -30
  81. aethergraph/services/scope/scope_factory.py +15 -7
  82. aethergraph/services/skills/__init__.py +0 -0
  83. aethergraph/services/skills/skill_registry.py +465 -0
  84. aethergraph/services/skills/skills.py +220 -0
  85. aethergraph/services/skills/utils.py +194 -0
  86. aethergraph/storage/artifacts/artifact_index_jsonl.py +16 -10
  87. aethergraph/storage/artifacts/artifact_index_sqlite.py +12 -2
  88. aethergraph/storage/docstore/sqlite_doc_sync.py +1 -1
  89. aethergraph/storage/memory/event_persist.py +42 -2
  90. aethergraph/storage/memory/fs_persist.py +32 -2
  91. aethergraph/storage/search_backend/__init__.py +0 -0
  92. aethergraph/storage/search_backend/generic_vector_backend.py +230 -0
  93. aethergraph/storage/search_backend/null_backend.py +34 -0
  94. aethergraph/storage/search_backend/sqlite_lexical_backend.py +387 -0
  95. aethergraph/storage/search_backend/utils.py +31 -0
  96. aethergraph/storage/search_factory.py +75 -0
  97. aethergraph/storage/vector_index/faiss_index.py +72 -4
  98. aethergraph/storage/vector_index/sqlite_index.py +521 -52
  99. aethergraph/storage/vector_index/sqlite_index_vanila.py +311 -0
  100. aethergraph/storage/vector_index/utils.py +22 -0
  101. {aethergraph-0.1.0a3.dist-info → aethergraph-0.1.0a4.dist-info}/METADATA +1 -1
  102. {aethergraph-0.1.0a3.dist-info → aethergraph-0.1.0a4.dist-info}/RECORD +107 -63
  103. {aethergraph-0.1.0a3.dist-info → aethergraph-0.1.0a4.dist-info}/WHEEL +1 -1
  104. aethergraph/plugins/agents/default_chat_agent copy.py +0 -90
  105. aethergraph/server/ui_static/assets/index-BR5GtXcZ.css +0 -1
  106. aethergraph/server/ui_static/assets/index-CQ0HZZ83.js +0 -400
  107. aethergraph/services/eventhub/event_hub.py +0 -76
  108. aethergraph/services/llm/generic_client copy.py +0 -691
  109. aethergraph/services/prompts/file_store.py +0 -41
  110. {aethergraph-0.1.0a3.dist-info → aethergraph-0.1.0a4.dist-info}/entry_points.txt +0 -0
  111. {aethergraph-0.1.0a3.dist-info → aethergraph-0.1.0a4.dist-info}/licenses/LICENSE +0 -0
  112. {aethergraph-0.1.0a3.dist-info → aethergraph-0.1.0a4.dist-info}/licenses/NOTICE +0 -0
  113. {aethergraph-0.1.0a3.dist-info → aethergraph-0.1.0a4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,369 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ import inspect
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from aethergraph.api.v1.deps import RequestIdentity
8
+ from aethergraph.contracts.services.llm import LLMClientProtocol
9
+ from aethergraph.core.runtime.run_types import (
10
+ RunImportance,
11
+ RunOrigin,
12
+ RunVisibility,
13
+ )
14
+
15
+ if TYPE_CHECKING:
16
+ from aethergraph.core.runtime.run_manager import RunManager
17
+
18
+ from .action_catalog import ActionCatalog
19
+ from .input_parser import InputParser, ParsedInputs
20
+ from .plan_executor import (
21
+ BackgroundExecutionHandle,
22
+ ExecutionResult,
23
+ OnCompleteCallback,
24
+ PlanExecutor,
25
+ )
26
+ from .plan_types import (
27
+ CandidatePlan,
28
+ ExecutionEventCallback,
29
+ PlanningContext,
30
+ PlanningEvent,
31
+ PlanningEventCallback,
32
+ PlanResult,
33
+ ValidationResult,
34
+ )
35
+ from .planner import ActionPlanner
36
+
37
+
38
+ @dataclass
39
+ class PlannerService:
40
+ """
41
+ High-level planning facade exposed as `context.planner()`.
42
+
43
+ Internally composes:
44
+ - ActionCatalog
45
+ - ActionPlanner
46
+ - PlanExecutor
47
+ - InputParser
48
+ """
49
+
50
+ catalog: ActionCatalog
51
+ llm: LLMClientProtocol
52
+ validator: Any # FlowValidator
53
+ run_manager: RunManager | None = None # for executing plans
54
+
55
+ # Lazily initialized components
56
+ _planner: ActionPlanner | None = None
57
+ _executor: PlanExecutor | None = None
58
+ _input_parser: InputParser | None = None
59
+
60
+ @property
61
+ def planner_core(self) -> ActionPlanner:
62
+ if self._planner is None:
63
+ self._planner = ActionPlanner(
64
+ catalog=self.catalog,
65
+ validator=self.validator,
66
+ llm=self.llm,
67
+ )
68
+ return self._planner
69
+
70
+ @property
71
+ def executor(self) -> PlanExecutor:
72
+ if self._executor is None:
73
+ self._executor = PlanExecutor(catalog=self.catalog, run_manager=self.run_manager)
74
+ return self._executor
75
+
76
+ @property
77
+ def input_parser(self) -> InputParser:
78
+ if self._input_parser is None:
79
+ self._input_parser = InputParser(llm=self.llm)
80
+ return self._input_parser
81
+
82
+ # ------------- Planning API -------------
83
+ async def plan(
84
+ self,
85
+ *,
86
+ goal: str,
87
+ user_inputs: dict[str, Any] | None = None,
88
+ external_slots: dict[str, Any] | None = None, # advanced use with IO slots
89
+ flow_ids: list[str] | None = None,
90
+ instruction: str | None = None,
91
+ allow_partial: bool = True,
92
+ preferred_external_keys: list[str] | None = None,
93
+ memory_snippets: list[str] | None = None,
94
+ artifact_snippets: list[str] | None = None,
95
+ on_event: PlanningEventCallback | None = None,
96
+ ) -> PlanResult:
97
+ """
98
+ Ergonomic entrypoint: build a PlanningContext and delegate to plan_with_context().
99
+ """
100
+ ctx = PlanningContext(
101
+ goal=goal,
102
+ user_inputs=user_inputs or {},
103
+ external_slots=external_slots or {},
104
+ memory_snippets=list(memory_snippets or []),
105
+ artifact_snippets=list(artifact_snippets or []),
106
+ flow_ids=list(flow_ids) if flow_ids is not None else None,
107
+ instruction=instruction,
108
+ allow_partial_plans=allow_partial,
109
+ preferred_external_keys=list(preferred_external_keys or []),
110
+ )
111
+ return await self.plan_with_context(ctx, on_event=on_event)
112
+
113
+ async def plan_with_context(
114
+ self,
115
+ ctx: PlanningContext,
116
+ *,
117
+ on_event: PlanningEventCallback | None = None,
118
+ ) -> PlanResult:
119
+ """
120
+ Plan towards the goal in the given context.
121
+ Emits PlanningEvents via on_event callback.
122
+ """
123
+ events: list[PlanningEvent] = []
124
+
125
+ async def _capture(ev: PlanningEvent) -> None:
126
+ # Always record locally
127
+ events.append(ev)
128
+
129
+ # Forward to caller if provided
130
+ if on_event:
131
+ result = on_event(ev)
132
+ if inspect.isawaitable(result):
133
+ await result
134
+
135
+ plan, history = await self.planner_core.plan_with_loop(
136
+ ctx,
137
+ on_event=_capture,
138
+ )
139
+
140
+ validation: ValidationResult | None
141
+ validation = history[-1] if history else None
142
+
143
+ return PlanResult(plan=plan, validation=validation, events=events)
144
+
145
+ # ------------- input parsing API -------------
146
+ async def parse_inputs(
147
+ self,
148
+ *,
149
+ message: str,
150
+ missing_keys: list[str],
151
+ instruction: str | None = None, # currently unused, keep for future use
152
+ ) -> ParsedInputs:
153
+ """
154
+ Parse user message into structured inputs for the given inputs.
155
+ """
156
+ # For now we ignore `instruction` and rely on meta,
157
+ # We can thread it into the system prompt later.
158
+ return await self.input_parser.parse_message_for_fields(
159
+ message=message,
160
+ missing_keys=missing_keys,
161
+ instruction=instruction,
162
+ )
163
+
164
+ async def execute_plan(
165
+ self,
166
+ plan: CandidatePlan,
167
+ *,
168
+ user_inputs: dict[str, Any] | None = None,
169
+ on_event: ExecutionEventCallback | None = None,
170
+ identity: RequestIdentity | None = None,
171
+ visibility: RunVisibility = RunVisibility.normal,
172
+ importance: RunImportance = RunImportance.normal,
173
+ session_id: str | None = None,
174
+ agent_id: str | None = None,
175
+ app_id: str | None = None,
176
+ tags: list[str] | None = None,
177
+ origin: RunOrigin | None = None,
178
+ ) -> ExecutionResult:
179
+ return await self.executor.execute(
180
+ plan,
181
+ user_inputs=user_inputs,
182
+ on_event=on_event,
183
+ identity=identity,
184
+ visibility=visibility,
185
+ importance=importance,
186
+ session_id=session_id,
187
+ agent_id=agent_id,
188
+ app_id=app_id,
189
+ tags=tags,
190
+ origin=origin,
191
+ )
192
+
193
+ async def execute_background(
194
+ self,
195
+ plan: CandidatePlan,
196
+ *,
197
+ user_inputs: dict[str, Any] | None = None,
198
+ on_event: ExecutionEventCallback | None = None,
199
+ identity: RequestIdentity | None = None,
200
+ visibility: RunVisibility = RunVisibility.normal,
201
+ importance: RunImportance = RunImportance.normal,
202
+ session_id: str | None = None,
203
+ agent_id: str | None = None,
204
+ app_id: str | None = None,
205
+ tags: list[str] | None = None,
206
+ origin: RunOrigin | None = None,
207
+ on_complete: OnCompleteCallback | None = None,
208
+ exec_id: str | None = None,
209
+ ) -> BackgroundExecutionHandle:
210
+ """
211
+ Schedule plan execution in the background and return a handle.
212
+
213
+ This is async only for API consistency with other services;
214
+ it returns as soon as the background task is scheduled.
215
+ """
216
+ handle = self.executor.execute_background(
217
+ plan,
218
+ user_inputs=user_inputs,
219
+ on_event=on_event,
220
+ identity=identity,
221
+ visibility=visibility,
222
+ importance=importance,
223
+ session_id=session_id,
224
+ agent_id=agent_id,
225
+ app_id=app_id,
226
+ tags=tags,
227
+ origin=origin,
228
+ on_complete=on_complete,
229
+ exec_id=exec_id,
230
+ )
231
+ return handle
232
+
233
+ async def plan_and_execute(
234
+ self,
235
+ *,
236
+ goal: str,
237
+ user_inputs: dict[str, Any] | None = None,
238
+ external_slots: dict[str, Any] | None = None,
239
+ flow_ids: list[str] | None = None,
240
+ instruction: str | None = None,
241
+ allow_partial: bool = False,
242
+ preferred_external_keys: list[str] | None = None,
243
+ memory_snippets: list[str] | None = None,
244
+ artifact_snippets: list[str] | None = None,
245
+ planning_events_cb: PlanningEventCallback | None = None,
246
+ execution_events_cb: ExecutionEventCallback | None = None,
247
+ identity: RequestIdentity | None = None,
248
+ visibility: RunVisibility | None = RunVisibility.normal,
249
+ importance: RunImportance | None = RunImportance.normal,
250
+ session_id: str | None = None,
251
+ agent_id: str | None = None,
252
+ app_id: str | None = None,
253
+ tags: list[str] | None = None,
254
+ origin: RunOrigin | None = None,
255
+ ) -> tuple[PlanResult, ExecutionResult | None]:
256
+ """
257
+ Convenience: plan first, then execute if plan is acceptable.
258
+ """
259
+ plan_result = await self.plan(
260
+ goal=goal,
261
+ user_inputs=user_inputs,
262
+ external_slots=external_slots,
263
+ flow_ids=flow_ids,
264
+ instruction=instruction,
265
+ allow_partial=allow_partial,
266
+ preferred_external_keys=preferred_external_keys,
267
+ memory_snippets=memory_snippets,
268
+ artifact_snippets=artifact_snippets,
269
+ on_event=planning_events_cb,
270
+ )
271
+
272
+ plan = plan_result.plan
273
+ validation = plan_result.validation
274
+
275
+ if plan is None or validation is None:
276
+ return plan_result, None
277
+
278
+ accept_partial = allow_partial and validation.is_partial_ok()
279
+ if not validation.ok and not accept_partial:
280
+ # Invalid plan and partial not allowed → do not execute
281
+ return plan_result, None
282
+
283
+ exec_result = await self.execute_plan(
284
+ plan,
285
+ user_inputs=user_inputs,
286
+ on_event=execution_events_cb,
287
+ identity=identity,
288
+ visibility=visibility,
289
+ importance=importance,
290
+ session_id=session_id,
291
+ agent_id=agent_id,
292
+ app_id=app_id,
293
+ tags=tags,
294
+ origin=origin,
295
+ )
296
+
297
+ return plan_result, exec_result
298
+
299
+ async def plan_and_execute_background(
300
+ self,
301
+ *,
302
+ goal: str,
303
+ user_inputs: dict[str, Any] | None = None,
304
+ external_slots: dict[str, Any] | None = None,
305
+ flow_ids: list[str] | None = None,
306
+ instruction: str | None = None,
307
+ allow_partial: bool = False,
308
+ preferred_external_keys: list[str] | None = None,
309
+ memory_snippets: list[str] | None = None,
310
+ artifact_snippets: list[str] | None = None,
311
+ planning_events_cb: PlanningEventCallback | None = None,
312
+ execution_events_cb: ExecutionEventCallback | None = None,
313
+ identity: RequestIdentity | None = None,
314
+ visibility: RunVisibility | None = RunVisibility.normal,
315
+ importance: RunImportance | None = RunImportance.normal,
316
+ session_id: str | None = None,
317
+ agent_id: str | None = None,
318
+ app_id: str | None = None,
319
+ tags: list[str] | None = None,
320
+ origin: RunOrigin | None = None,
321
+ on_complete: OnCompleteCallback | None = None,
322
+ exec_id: str | None = None,
323
+ ) -> tuple[PlanResult, BackgroundExecutionHandle | None]:
324
+ """
325
+ Plan first, then execute the plan in the background if acceptable.
326
+
327
+ Returns:
328
+ (PlanResult, BackgroundExecutionHandle | None)
329
+ """
330
+ plan_result = await self.plan(
331
+ goal=goal,
332
+ user_inputs=user_inputs,
333
+ external_slots=external_slots,
334
+ flow_ids=flow_ids,
335
+ instruction=instruction,
336
+ allow_partial=allow_partial,
337
+ preferred_external_keys=preferred_external_keys,
338
+ memory_snippets=memory_snippets,
339
+ artifact_snippets=artifact_snippets,
340
+ on_event=planning_events_cb,
341
+ )
342
+
343
+ plan = plan_result.plan
344
+ validation = plan_result.validation
345
+
346
+ if plan is None or validation is None:
347
+ return plan_result, None
348
+
349
+ accept_partial = allow_partial and validation.is_partial_ok()
350
+ if not validation.ok and not accept_partial:
351
+ return plan_result, None
352
+
353
+ handle = await self.execute_background(
354
+ plan,
355
+ user_inputs=user_inputs,
356
+ on_event=execution_events_cb,
357
+ identity=identity,
358
+ visibility=visibility,
359
+ importance=importance,
360
+ session_id=session_id,
361
+ agent_id=agent_id,
362
+ app_id=app_id,
363
+ tags=tags,
364
+ origin=origin,
365
+ on_complete=on_complete,
366
+ exec_id=exec_id,
367
+ )
368
+
369
+ return plan_result, handle
@@ -0,0 +1,43 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+ from aethergraph.contracts.services.planning import (
7
+ PlanningContextBuilderProtocol,
8
+ RoutedIntent,
9
+ SessionState,
10
+ )
11
+ from aethergraph.services.planning.planner import PlanningContext
12
+
13
+
14
+ @dataclass
15
+ class SimplePlanningContextBuilder(PlanningContextBuilderProtocol):
16
+ """
17
+ Baseline builder: treat the raw user message as goal and
18
+ pass through user_inputs as-is (you can enrich with RAG later).
19
+ """
20
+
21
+ def _extract_user_inputs(self, user_message: str) -> dict[str, Any]:
22
+ # For now: rely on external caller to provide user_inputs;
23
+ # later add LLM-based extraction or explicit forms.
24
+ return {}
25
+
26
+ async def build(
27
+ self,
28
+ *,
29
+ user_message: str,
30
+ routed: RoutedIntent,
31
+ session_state: SessionState,
32
+ ) -> PlanningContext:
33
+ goal = user_message.strip()
34
+ user_inputs = self._extract_user_inputs(user_message)
35
+
36
+ return PlanningContext(
37
+ goal=goal,
38
+ user_inputs=user_inputs,
39
+ external_slots={}, # you can thread structured external inputs later
40
+ memory_snippets=[], # future: fetch from ctx.memory()
41
+ artifact_snippets=[], # future: fetch from indices/artifacts
42
+ flow_ids=routed.flow_ids,
43
+ )
@@ -0,0 +1,29 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Protocol
5
+
6
+
7
+ @dataclass
8
+ class QuickActionSpec:
9
+ id: str
10
+ description: str
11
+ # e.g. `list_recent_runs`, `open_notebook`, `answer_with_rag`
12
+ requires_confirmation: bool = False
13
+ action_ref: str | None = None # optional link to ActionSpec.ref
14
+
15
+
16
+ class QuickActionHandler(Protocol):
17
+ async def __call__(self, *, context: Any) -> Any: ...
18
+
19
+
20
+ @dataclass
21
+ class QuickActionRegistry:
22
+ actions: dict[str, QuickActionSpec]
23
+ handlers: dict[str, QuickActionHandler]
24
+
25
+ def get_spec(self, action_id: str) -> QuickActionSpec | None:
26
+ return self.actions.get(action_id)
27
+
28
+ def get_handler(self, action_id: str) -> QuickActionHandler | None:
29
+ return self.handlers.get(action_id)
File without changes
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from aethergraph.contracts.services.planning import IntentRouter, RoutedIntent, SessionState
6
+
7
+
8
+ @dataclass
9
+ class SimpleIntentRouter(IntentRouter):
10
+ """
11
+ Docstring for SimpleIntentRouter
12
+ """
13
+
14
+ async def route(
15
+ self,
16
+ *,
17
+ user_message: str,
18
+ session_state: SessionState,
19
+ ) -> RoutedIntent:
20
+ # Simple routing logic: always plan and execute with no specific flows
21
+ return RoutedIntent(
22
+ mode="plan_and_execute",
23
+ flow_ids=None,
24
+ quick_action_id=None,
25
+ metadata={},
26
+ )
@@ -439,9 +439,6 @@ class RAGFacade:
439
439
  k: Number of top results to return.
440
440
  rerank: Whether to rerank results using hybrid scoring.
441
441
  """
442
- print(
443
- f"🍏 RAGFacade.retrieve: corpus_id={corpus_id}, query={query}, k={k}, rerank={rerank}"
444
- )
445
442
  # For now, rerank flag is ignored; fused hybrid already sorts reasonably.
446
443
  return await self.search(corpus_id, query, k=k, mode="hybrid")
447
444
 
@@ -14,6 +14,7 @@ class Scope:
14
14
 
15
15
  # App / execution context
16
16
  app_id: str | None = None
17
+ agent_id: str | None = None
17
18
  session_id: str | None = None
18
19
  run_id: str | None = None
19
20
  graph_id: str | None = None
@@ -36,10 +37,13 @@ class Scope:
36
37
  def get(self, key: str, default: Any = None) -> Any:
37
38
  return getattr(self, key, default)
38
39
 
39
- def artifact_scope_labels(self) -> dict[str, str]:
40
+ def _base_identity_labels(self) -> dict[str, str]:
40
41
  """
41
- Labels to attach to every artifact for this scope.
42
- These will be mirrored both into Artifact.labels and the index.
42
+ Docstring for _base_identity_labels
43
+
44
+ :param self: Description
45
+ :return: Description
46
+ :rtype: dict[str, str]
43
47
  """
44
48
  out: dict[str, str] = {}
45
49
  if self.org_id:
@@ -50,6 +54,8 @@ class Scope:
50
54
  out["client_id"] = self.client_id
51
55
  if self.app_id:
52
56
  out["app_id"] = self.app_id
57
+ if self.agent_id:
58
+ out["agent_id"] = self.agent_id
53
59
  if self.session_id:
54
60
  out["session_id"] = self.session_id
55
61
  if self.run_id:
@@ -58,16 +64,21 @@ class Scope:
58
64
  out["graph_id"] = self.graph_id
59
65
  if self.node_id:
60
66
  out["node_id"] = self.node_id
67
+ if self.flow_id:
68
+ out["flow_id"] = self.flow_id
69
+ return out
61
70
 
62
- # canonicial scope ids
63
- if self.session_id:
64
- out["scope_id"] = self.session_id # session-centric
65
- elif self.run_id:
66
- out["scope_id"] = self.run_id # run-centric for non-session runs
67
- elif self.graph_id:
68
- out["scope_id"] = f"graph:{self.graph_id}" # graph-centric for non-run artifacts
69
- elif self.node_id:
70
- out["scope_id"] = f"node:{self.node_id}" # node-centric for non-graph artifacts
71
+ def artifact_scope_labels(self) -> dict[str, str]:
72
+ """
73
+ Labels to attach to every artifact for this scope.
74
+ These will be mirrored both into Artifact.labels and the index.
75
+ """
76
+ out: dict[str, str] = {}
77
+ out.update(self._base_identity_labels())
78
+
79
+ # Cononical scope id for artifacts == memory scope id
80
+ # So filter the memory + artifacts by the same value
81
+ out["scope_id"] = self.memory_scope_id()
71
82
  return out
72
83
 
73
84
  def metering_dimensions(self) -> dict[str, Any]:
@@ -123,22 +134,7 @@ class Scope:
123
134
  but can be any logical scope key.
124
135
  """
125
136
  out: dict[str, Any] = {}
126
- if self.user_id:
127
- out["user_id"] = self.user_id
128
- if self.org_id:
129
- out["org_id"] = self.org_id
130
- if self.client_id:
131
- out["client_id"] = self.client_id
132
- if self.app_id:
133
- out["app_id"] = self.app_id
134
- if self.session_id:
135
- out["session_id"] = self.session_id
136
- if self.run_id:
137
- out["run_id"] = self.run_id
138
- if self.graph_id:
139
- out["graph_id"] = self.graph_id
140
- if self.node_id:
141
- out["node_id"] = self.node_id
137
+ out.update(self._base_identity_labels())
142
138
  if scope_id:
143
139
  out["scope_id"] = scope_id
144
140
  return out
@@ -146,14 +142,18 @@ class Scope:
146
142
  def rag_filter(self, *, scope_id: str | None = None) -> dict[str, Any]:
147
143
  """
148
144
  Default filter for RAG search based on identity.
149
- You can adjust strictness (e.g., ignore run_id for per-user corpora).
145
+
146
+ By default we isolate on:
147
+ - org_id (tenant)
148
+ - user_id (actor within tenant)
149
+ - scope_id (memory bucket, usually memory_scope_id)
150
150
  """
151
151
  out: dict[str, Any] = {}
152
152
  if self.user_id:
153
153
  out["user_id"] = self.user_id
154
154
  if self.org_id:
155
155
  out["org_id"] = self.org_id
156
+ # scope_id is usually memory_scope_id() (e.g. session:, user:, run:, org:, app:)
156
157
  if scope_id:
157
158
  out["scope_id"] = scope_id
158
- # you can choose to include session_id / run_id only for very strict isolation
159
159
  return out
@@ -63,12 +63,19 @@ class ScopeFactory:
63
63
  session_id: str | None = None,
64
64
  flow_id: str | None = None,
65
65
  ) -> Scope:
66
- s = self.base_from_identity(identity)
67
- s.run_id = run_id
68
- s.graph_id = graph_id
69
- s.session_id = session_id
70
- s.flow_id = flow_id
71
- return s
66
+ base = self.base_from_identity(identity)
67
+ return Scope(
68
+ org_id=base.org_id,
69
+ user_id=base.user_id,
70
+ client_id=base.client_id,
71
+ mode=base.mode,
72
+ app_id=base.app_id,
73
+ session_id=session_id,
74
+ run_id=run_id,
75
+ graph_id=graph_id,
76
+ node_id=None,
77
+ flow_id=flow_id,
78
+ )
72
79
 
73
80
  def for_memory_custom_override(
74
81
  self,
@@ -148,8 +155,9 @@ class ScopeFactory:
148
155
  base = session_id or run_id
149
156
  root = f"session:{base}"
150
157
  elif level == "user":
158
+ o = s.org_id or "orgless"
151
159
  u = s.user_id or s.client_id or "anon"
152
- root = f"user:{u}"
160
+ root = f"org:{o}:user:{u}"
153
161
  elif level == "run":
154
162
  root = f"run:{run_id}"
155
163
  elif level == "org":
File without changes