agnt5 0.2.2__cp39-abi3-macosx_11_0_arm64.whl → 0.2.4__cp39-abi3-macosx_11_0_arm64.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.

Potentially problematic release.


This version of agnt5 might be problematic. Click here for more details.

agnt5/context.py CHANGED
@@ -3,802 +3,82 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import logging
6
- from typing import Any, Awaitable, Callable, Dict, List, Optional, TypeVar
7
-
8
- from .exceptions import NotImplementedError as AGNT5NotImplementedError
9
- from .exceptions import StateError
6
+ from typing import Any, Awaitable, Callable, Dict, List, Optional, TypeVar, Union
10
7
 
11
8
  T = TypeVar("T")
12
9
 
13
10
 
14
- class StateClient:
15
- """
16
- Workflow state management client for durable workflows.
17
-
18
- Provides simple key-value state storage scoped to workflow execution.
19
- State changes are tracked and persisted for durability.
20
-
21
- Example:
22
- ```python
23
- @workflow
24
- async def order_flow(ctx: Context, order_id: str):
25
- await ctx.state.set("status", "processing")
26
- await ctx.state.set("order_id", order_id)
27
-
28
- # State persists across steps
29
- result = await ctx.task("service", "process", input=order_id)
30
-
31
- status = await ctx.state.get("status") # "processing"
32
- return {"status": status, "result": result}
33
- ```
34
- """
35
-
36
- def __init__(self, ctx: "Context") -> None:
37
- """Initialize state client."""
38
- self._ctx = ctx
39
- self._state: Dict[str, Any] = {} # Current state
40
- self._state_changes: List[Dict[str, Any]] = [] # Track changes for events
41
-
42
- async def get(self, key: str, default: Any = None) -> Any:
43
- """
44
- Get workflow state value.
45
-
46
- Args:
47
- key: State key
48
- default: Default value if key doesn't exist
49
-
50
- Returns:
51
- Value associated with key, or default
52
-
53
- Example:
54
- ```python
55
- retry_count = await ctx.state.get("retry_count", 0)
56
- ```
57
- """
58
- return self._state.get(key, default)
59
-
60
- async def set(self, key: str, value: Any) -> None:
61
- """
62
- Set workflow state value.
63
-
64
- Args:
65
- key: State key
66
- value: Value to store (must be JSON-serializable)
67
-
68
- Example:
69
- ```python
70
- await ctx.state.set("status", "completed")
71
- await ctx.state.set("retry_count", 3)
72
- ```
73
- """
74
- self._state[key] = value
75
- self._state_changes.append({"key": key, "value": value, "deleted": False})
76
-
77
- async def delete(self, key: str) -> None:
78
- """
79
- Delete workflow state key.
80
-
81
- Args:
82
- key: State key to delete
83
-
84
- Example:
85
- ```python
86
- await ctx.state.delete("temporary_data")
87
- ```
88
- """
89
- self._state.pop(key, None)
90
- self._state_changes.append({"key": key, "value": None, "deleted": True})
91
-
92
- async def clear(self) -> None:
93
- """
94
- Clear all workflow state.
95
-
96
- Example:
97
- ```python
98
- await ctx.state.clear()
99
- ```
100
- """
101
- self._state.clear()
102
- self._state_changes.append({"key": "__clear__", "value": None, "deleted": True})
11
+ class _CorrelationFilter(logging.Filter):
12
+ """Inject correlation IDs (run_id, trace_id, span_id) into every log record."""
103
13
 
104
- def get_state_snapshot(self) -> Dict[str, Any]:
105
- """Get full state snapshot for persistence (internal use)."""
106
- return dict(self._state)
14
+ def __init__(self, runtime_context: Any) -> None:
15
+ super().__init__()
16
+ self.runtime_context = runtime_context
107
17
 
108
- def has_changes(self) -> bool:
109
- """Check if state has any changes (internal use)."""
110
- return len(self._state_changes) > 0
18
+ def filter(self, record: logging.LogRecord) -> bool:
19
+ """Add correlation IDs as extra fields to the log record."""
20
+ record.run_id = self.runtime_context.run_id
21
+ if self.runtime_context.trace_id:
22
+ record.trace_id = self.runtime_context.trace_id
23
+ if self.runtime_context.span_id:
24
+ record.span_id = self.runtime_context.span_id
25
+ return True
111
26
 
112
27
 
113
28
  class Context:
114
29
  """
115
- Execution context for AGNT5 components (functions, entities, workflows).
30
+ Base context providing common functionality.
116
31
 
117
- Provides APIs for:
118
- - Orchestration: task(), parallel(), gather(), spawn()
119
- - State Management: get(), set(), delete()
120
- - Coordination: signal(), timer(), sleep()
121
- - AI Integration: llm.generate(), llm.stream()
122
- - Observability: log(), metrics(), trace_span()
32
+ Provides:
33
+ - Logging with correlation IDs
34
+ - Execution metadata (run_id, attempt)
35
+ - Runtime context for tracing
36
+
37
+ Extended by:
38
+ - FunctionContext: Minimal context for stateless functions
39
+ - WorkflowContext: Context for durable workflows
123
40
  """
124
41
 
125
42
  def __init__(
126
43
  self,
127
44
  run_id: str,
128
- component_type: str = "function",
129
- step_id: Optional[str] = None,
130
45
  attempt: int = 0,
131
- object_id: Optional[str] = None,
132
- method_name: Optional[str] = None,
133
- completed_steps: Optional[Dict[str, Any]] = None, # Phase 6B: Replay support
134
- initial_state: Optional[Dict[str, Any]] = None, # Phase 6B: State replay
46
+ runtime_context: Optional[Any] = None,
135
47
  ) -> None:
136
- """Initialize context with execution metadata.
48
+ """
49
+ Initialize base context.
137
50
 
138
51
  Args:
139
- run_id: Unique run identifier
140
- component_type: Type of component ('function', 'workflow', 'entity')
141
- step_id: Current step identifier (optional)
142
- attempt: Retry attempt number (default 0)
143
- object_id: Entity key for entity components (optional)
144
- method_name: Entity method name (optional)
145
- completed_steps: Pre-recorded steps for workflow replay (Phase 6B)
146
- initial_state: Initial workflow state for replay (Phase 6B)
52
+ run_id: Unique execution identifier
53
+ attempt: Retry attempt number (0-indexed)
54
+ runtime_context: RuntimeContext for trace correlation
147
55
  """
148
56
  self._run_id = run_id
149
- self._component_type = component_type
150
- self._step_id = step_id
151
57
  self._attempt = attempt
152
- self._object_id = object_id
153
- self._method_name = method_name
154
- self._state: Dict[str, Any] = {}
155
- self._checkpoints: Dict[str, Any] = {}
58
+ self._runtime_context = runtime_context
156
59
 
157
- # Create logger with OpenTelemetry integration
60
+ # Create logger with correlation
158
61
  self._logger = logging.getLogger(f"agnt5.{run_id}")
159
62
  from ._telemetry import setup_context_logger
160
63
  setup_context_logger(self._logger)
161
64
 
162
- # Workflow durability: step tracking (Phase 6A)
163
- self._step_counter: int = 0 # Track step sequence for workflows
164
- self._completed_steps: Dict[str, Any] = completed_steps or {} # Cache for replay (Phase 6B)
165
- self._step_events: List[Dict[str, Any]] = [] # Collect events to publish
166
-
167
- # Phase 6B: Track if we're replaying from cached steps
168
- self._is_replay = len(self._completed_steps) > 0
169
- if self._is_replay:
170
- self._logger.info(f"🔄 Replaying workflow with {len(self._completed_steps)} cached steps")
171
-
172
- # Phase 6B: Initialize state client with initial state for replay
173
- if initial_state:
174
- self._state_client = StateClient(self)
175
- self._state_client._state = initial_state.copy()
176
- self._logger.info(f"🔄 Loaded workflow state: {len(initial_state)} keys")
177
-
178
- # === Execution Metadata ===
65
+ if runtime_context:
66
+ self._logger.addFilter(_CorrelationFilter(runtime_context))
179
67
 
180
68
  @property
181
69
  def run_id(self) -> str:
182
- """Workflow/run identifier."""
70
+ """Unique execution identifier."""
183
71
  return self._run_id
184
72
 
185
- @property
186
- def step_id(self) -> Optional[str]:
187
- """Current step identifier."""
188
- return self._step_id
189
-
190
73
  @property
191
74
  def attempt(self) -> int:
192
- """Retry attempt number."""
75
+ """Current retry attempt (0-indexed)."""
193
76
  return self._attempt
194
77
 
195
- @property
196
- def component_type(self) -> str:
197
- """Component type: 'function', 'entity', 'workflow'."""
198
- return self._component_type
199
-
200
- @property
201
- def object_id(self) -> Optional[str]:
202
- """Entity key (for entities only)."""
203
- return self._object_id
204
-
205
- @property
206
- def method_name(self) -> Optional[str]:
207
- """Entity method name (for entities only)."""
208
- return self._method_name
209
-
210
- # === State Management ===
211
-
212
- def get(self, key: str, default: Any = None) -> Any:
213
- """
214
- Get value from state (synchronous).
215
-
216
- For method-based entities (@entity.method):
217
- count = ctx.get("count", 0)
218
- """
219
- return self._state.get(key, default)
220
-
221
- def set(self, key: str, value: Any) -> None:
222
- """
223
- Set value in state (synchronous).
224
-
225
- For method-based entities (@entity.method):
226
- ctx.set("count", 5)
227
- """
228
- self._state[key] = value
229
-
230
- def delete(self, key: str) -> None:
231
- """
232
- Delete key from state (synchronous).
233
-
234
- For method-based entities (@entity.method):
235
- ctx.delete("count")
236
- """
237
- try:
238
- del self._state[key]
239
- except KeyError:
240
- raise StateError(f"Key '{key}' not found in state")
241
-
242
- async def get_async(self, key: str, default: Any = None) -> Any:
243
- """
244
- Get value from state (async version for DurableEntity).
245
-
246
- For class-based entities (DurableEntity):
247
- count = await self.ctx.get_async("count", 0)
248
- # Or use the alias: await self.ctx.get(...)
249
- """
250
- return self._state.get(key, default)
251
-
252
- async def set_async(self, key: str, value: Any) -> None:
253
- """
254
- Set value in state (async version for DurableEntity).
255
-
256
- For class-based entities (DurableEntity):
257
- await self.ctx.set_async("count", 5)
258
- # Or use the alias: await self.ctx.set(...)
259
- """
260
- self._state[key] = value
261
-
262
- async def delete_async(self, key: str) -> None:
263
- """
264
- Delete key from state (async version for DurableEntity).
265
-
266
- For class-based entities (DurableEntity):
267
- await self.ctx.delete_async("count")
268
- # Or use the alias: await self.ctx.delete(...)
269
- """
270
- try:
271
- del self._state[key]
272
- except KeyError:
273
- raise StateError(f"Key '{key}' not found in state")
274
-
275
- async def clear_all(self) -> None:
276
- """Clear all state (async)."""
277
- self._state.clear()
278
-
279
- # === Checkpointing ===
280
-
281
- async def step(self, name: str, func: Callable[[], Awaitable[T]]) -> T:
282
- """
283
- Checkpoint expensive operations.
284
-
285
- If function crashes, won't re-execute this step on retry.
286
- """
287
- # Check if step already executed
288
- if name in self._checkpoints:
289
- return self._checkpoints[name]
290
-
291
- # Execute and checkpoint
292
- result = await func()
293
- self._checkpoints[name] = result
294
- return result
295
-
296
- # === Orchestration (Workflows Only) ===
297
-
298
- async def task(
299
- self,
300
- service_name: str,
301
- handler_name: str,
302
- input: Any = None,
303
- ) -> Any:
304
- """
305
- Execute a function and wait for result (workflows only).
306
-
307
- Phase 1: Calls local @function handlers directly.
308
- Phase 6: Records step completions for workflow durability.
309
- Phase 2: Will support distributed function execution across services.
310
-
311
- Args:
312
- service_name: Service name (ignored in Phase 1)
313
- handler_name: Function handler name
314
- input: Input data for the function
315
-
316
- Returns:
317
- Function result
318
-
319
- Example:
320
- ```python
321
- result = await ctx.task(
322
- service_name="data-processor",
323
- handler_name="process_data",
324
- input={"data": [1, 2, 3]}
325
- )
326
- ```
327
- """
328
- from .function import FunctionRegistry
329
-
330
- # Phase 6: Generate unique step name for durability
331
- step_name = f"{handler_name}_{self._step_counter}"
332
- self._step_counter += 1
333
-
334
- # Phase 6B: Check if we're in replay mode (step already completed)
335
- if step_name in self._completed_steps:
336
- # REPLAY: Return cached result without re-executing
337
- result = self._completed_steps[step_name]
338
- self._logger.info(f"🔄 Replaying cached step: {step_name} (skipping execution)")
339
- # Don't record again - already in database
340
- return result
341
-
342
- # NORMAL EXECUTION: Look up function in registry
343
- self._logger.info(f"▶️ Executing new step: {step_name}")
344
- func_config = FunctionRegistry.get(handler_name)
345
- if func_config is None:
346
- raise ValueError(f"Function '{handler_name}' not found in registry")
347
-
348
- # Create child context for function execution
349
- child_ctx = Context(
350
- run_id=f"{self.run_id}:task:{handler_name}",
351
- component_type="function",
352
- )
353
-
354
- # Execute function with input
355
- if input is not None:
356
- result = await func_config.handler(child_ctx, input)
357
- else:
358
- result = await func_config.handler(child_ctx)
359
-
360
- # Phase 6: Record step completion for durability
361
- self._record_step_completion(step_name, handler_name, input, result)
362
-
363
- return result
364
-
365
- def _record_step_completion(
366
- self, step_name: str, handler_name: str, input: Any, result: Any
367
- ) -> None:
368
- """
369
- Record step completion event for workflow durability (internal use).
370
-
371
- Args:
372
- step_name: Unique step identifier
373
- handler_name: Function handler name
374
- input: Input data passed to function
375
- result: Function result
376
- """
377
- self._step_events.append({
378
- "step_name": step_name,
379
- "handler_name": handler_name,
380
- "input": input,
381
- "result": result,
382
- })
383
- self._logger.debug(f"Recorded step completion: {step_name}")
384
-
385
- async def parallel(self, *tasks: Awaitable[T]) -> List[T]:
386
- """
387
- Run multiple tasks in parallel (workflows only).
388
-
389
- Phase 1: Uses asyncio.gather() for in-process parallelism.
390
- Phase 2: Will add distributed execution across services.
391
-
392
- Args:
393
- *tasks: Async tasks to run in parallel
394
-
395
- Returns:
396
- List of results in the same order as tasks
397
-
398
- Example:
399
- ```python
400
- result1, result2 = await ctx.parallel(
401
- fetch_data(source1),
402
- fetch_data(source2)
403
- )
404
- ```
405
- """
406
- import asyncio
407
- return list(await asyncio.gather(*tasks))
408
-
409
- async def gather(self, **tasks: Awaitable[T]) -> Dict[str, T]:
410
- """
411
- Run tasks in parallel with named results (workflows only).
412
-
413
- Phase 1: Uses asyncio.gather() for in-process parallelism.
414
- Phase 2: Will add distributed execution across services.
415
-
416
- Args:
417
- **tasks: Named async tasks to run in parallel
418
-
419
- Returns:
420
- Dictionary mapping names to results
421
-
422
- Example:
423
- ```python
424
- results = await ctx.gather(
425
- db=query_database(),
426
- api=fetch_api(),
427
- cache=check_cache()
428
- )
429
- # Access: results["db"], results["api"], results["cache"]
430
- ```
431
- """
432
- import asyncio
433
- keys = list(tasks.keys())
434
- values = list(tasks.values())
435
- results = await asyncio.gather(*values)
436
- return dict(zip(keys, results))
437
-
438
- def spawn(self, handler: Callable[..., Awaitable[T]], *args: Any, **kwargs: Any) -> Any:
439
- """
440
- Spawn a child function without waiting.
441
-
442
- Raises:
443
- AGNT5NotImplementedError: Not yet implemented in Phase 1
444
- """
445
- raise AGNT5NotImplementedError(
446
- "ctx.spawn() will be implemented in Phase 2 with platform integration"
447
- )
448
-
449
- # === Coordination ===
450
-
451
- async def signal(
452
- self,
453
- name: str,
454
- timeout_ms: Optional[int] = None,
455
- default: Any = None,
456
- ) -> Any:
457
- """
458
- Wait for external signal.
459
-
460
- Phase 1: Uses asyncio.Event for in-process signaling.
461
- Phase 2: Will add durable cross-process signal support.
462
-
463
- Args:
464
- name: Signal name to wait for
465
- timeout_ms: Optional timeout in milliseconds
466
- default: Default value if timeout occurs
467
-
468
- Returns:
469
- Signal payload or default value
470
-
471
- Example:
472
- ```python
473
- # In one task:
474
- await ctx.signal_send("approval", {"approved": True})
475
-
476
- # In another task:
477
- result = await ctx.signal("approval", timeout_ms=5000)
478
- ```
479
- """
480
- import asyncio
481
-
482
- # Get or create event for this signal
483
- if not hasattr(self, "_signals"):
484
- self._signals: Dict[str, tuple[asyncio.Event, Any]] = {}
485
-
486
- if name not in self._signals:
487
- event = asyncio.Event()
488
- self._signals[name] = (event, None)
489
-
490
- event, payload = self._signals[name]
491
-
492
- # Wait for signal with optional timeout
493
- try:
494
- if timeout_ms:
495
- await asyncio.wait_for(event.wait(), timeout=timeout_ms / 1000)
496
- else:
497
- await event.wait()
498
- return self._signals[name][1] # Return payload
499
- except asyncio.TimeoutError:
500
- return default
501
-
502
- def signal_send(self, name: str, payload: Any = None) -> None:
503
- """
504
- Send a signal to waiting tasks.
505
-
506
- Phase 1: Uses asyncio.Event for in-process signaling.
507
- Phase 2: Will add durable cross-process signal support.
508
-
509
- Args:
510
- name: Signal name
511
- payload: Data to send with signal
512
-
513
- Example:
514
- ```python
515
- # Send approval signal
516
- ctx.signal_send("approval", {"approved": True, "by": "admin"})
517
- ```
518
- """
519
- import asyncio
520
-
521
- # Get or create event for this signal
522
- if not hasattr(self, "_signals"):
523
- self._signals: Dict[str, tuple[asyncio.Event, Any]] = {}
524
-
525
- if name not in self._signals:
526
- event = asyncio.Event()
527
- self._signals[name] = (event, payload)
528
- else:
529
- # Update payload and set event
530
- event = self._signals[name][0]
531
- self._signals[name] = (event, payload)
532
-
533
- event.set()
534
-
535
- async def timer(
536
- self,
537
- delay_ms: Optional[int] = None,
538
- cron: Optional[str] = None,
539
- ) -> None:
540
- """
541
- Wait for delay or scheduled time.
542
-
543
- Phase 1: Supports delay_ms using asyncio.sleep (cron not supported).
544
- Phase 2: Will add durable timers and cron scheduling.
545
-
546
- Args:
547
- delay_ms: Delay in milliseconds
548
- cron: Cron expression (Phase 2 only)
549
-
550
- Example:
551
- ```python
552
- await ctx.timer(delay_ms=5000) # Wait 5 seconds
553
- ```
554
- """
555
- if cron is not None:
556
- raise AGNT5NotImplementedError(
557
- "ctx.timer(cron=...) will be implemented in Phase 2 with cron support"
558
- )
559
-
560
- if delay_ms is not None:
561
- import asyncio
562
- await asyncio.sleep(delay_ms / 1000)
563
-
564
- async def sleep(self, seconds: int) -> None:
565
- """
566
- Durable sleep (alternative to timer).
567
-
568
- Phase 1: Uses asyncio.sleep for in-process delays.
569
- Phase 2: Will add durable sleep that survives restarts.
570
-
571
- Args:
572
- seconds: Number of seconds to sleep
573
-
574
- Example:
575
- ```python
576
- await ctx.sleep(5) # Sleep 5 seconds
577
- ```
578
- """
579
- import asyncio
580
- await asyncio.sleep(seconds)
581
-
582
- # === Observability ===
583
-
584
- def log(self) -> logging.Logger:
585
- """Get structured logger for this context."""
586
- return self._logger
587
-
588
78
  @property
589
79
  def logger(self) -> logging.Logger:
590
- """Alias for log() - get logger."""
80
+ """Full logger for .debug(), .warning(), .error(), etc."""
591
81
  return self._logger
592
82
 
593
- def metrics(self) -> "MetricsClient":
594
- """
595
- Get metrics client.
596
-
597
- Raises:
598
- AGNT5NotImplementedError: Not yet implemented in Phase 1
599
- """
600
- raise AGNT5NotImplementedError(
601
- "ctx.metrics() will be implemented in Phase 2 with observability support"
602
- )
603
-
604
- def trace_span(self) -> "TraceSpan":
605
- """
606
- Create trace span.
607
-
608
- Raises:
609
- AGNT5NotImplementedError: Not yet implemented in Phase 1
610
- """
611
- raise AGNT5NotImplementedError(
612
- "ctx.trace_span() will be implemented in Phase 2 with observability support"
613
- )
614
-
615
- # === AI Integration ===
616
-
617
- @property
618
- def llm(self) -> "LLMClient":
619
- """
620
- Get LLM client for generate/stream.
621
-
622
- Raises:
623
- AGNT5NotImplementedError: Not yet implemented in Phase 1
624
- """
625
- raise AGNT5NotImplementedError(
626
- "ctx.llm will be implemented in Phase 2 with LLM support"
627
- )
628
-
629
- @property
630
- def tools(self) -> "ToolsClient":
631
- """
632
- Get tools client for registration.
633
-
634
- Raises:
635
- AGNT5NotImplementedError: Not yet implemented in Phase 1
636
- """
637
- raise AGNT5NotImplementedError(
638
- "ctx.tools will be implemented in Phase 2 with tools support"
639
- )
640
-
641
- # === Memory (Alternative State API) ===
642
-
643
- @property
644
- def state(self) -> StateClient:
645
- """
646
- Get state client for durable workflow state.
647
-
648
- Available for workflows only. Provides key-value state storage
649
- that persists across workflow steps and survives crashes.
650
-
651
- Returns:
652
- StateClient instance
653
-
654
- Example:
655
- ```python
656
- @workflow
657
- async def my_workflow(ctx: Context):
658
- await ctx.state.set("status", "processing")
659
- result = await ctx.task("service", "process", input={})
660
- status = await ctx.state.get("status")
661
- return {"status": status}
662
- ```
663
- """
664
- if not hasattr(self, '_state_client'):
665
- self._state_client = StateClient(self)
666
- return self._state_client
667
-
668
- @property
669
- def memory(self) -> "MemoryClient":
670
- """
671
- Get memory client for durable state operations.
672
-
673
- Raises:
674
- AGNT5NotImplementedError: Not yet implemented in Phase 1
675
- """
676
- raise AGNT5NotImplementedError(
677
- "ctx.memory will be implemented in Phase 2 with memory support"
678
- )
679
-
680
- # === Configuration & Secrets ===
681
-
682
- def secrets(self) -> "SecretsClient":
683
- """
684
- Access secrets securely.
685
-
686
- Raises:
687
- AGNT5NotImplementedError: Not yet implemented in Phase 1
688
- """
689
- raise AGNT5NotImplementedError(
690
- "ctx.secrets() will be implemented in Phase 2 with secrets support"
691
- )
692
-
693
- def config(self) -> "ConfigClient":
694
- """
695
- Access configuration and feature flags.
696
-
697
- Raises:
698
- AGNT5NotImplementedError: Not yet implemented in Phase 1
699
- """
700
- raise AGNT5NotImplementedError(
701
- "ctx.config() will be implemented in Phase 2 with config support"
702
- )
703
-
704
- def headers(self) -> Dict[str, str]:
705
- """
706
- Access request headers.
707
-
708
- Raises:
709
- AGNT5NotImplementedError: Not yet implemented in Phase 1
710
- """
711
- raise AGNT5NotImplementedError(
712
- "ctx.headers() will be implemented in Phase 2 with request context"
713
- )
714
-
715
- # === Messaging ===
716
-
717
- async def send_to(
718
- self,
719
- target: str,
720
- message: Any,
721
- metadata: Optional[Dict[str, Any]] = None,
722
- ) -> str:
723
- """
724
- Send message to another participant.
725
-
726
- Raises:
727
- AGNT5NotImplementedError: Not yet implemented in Phase 1
728
- """
729
- raise AGNT5NotImplementedError(
730
- "ctx.send_to() will be implemented in Phase 2 with messaging support"
731
- )
732
-
733
- async def subscribe(self, participant_id: str) -> Any:
734
- """
735
- Subscribe to messages.
736
-
737
- Raises:
738
- AGNT5NotImplementedError: Not yet implemented in Phase 1
739
- """
740
- raise AGNT5NotImplementedError(
741
- "ctx.subscribe() will be implemented in Phase 2 with messaging support"
742
- )
743
-
744
- # === Entity Methods ===
745
-
746
- def entity(self, entity_type: str, key: str) -> "EntityProxy":
747
- """
748
- Get entity proxy for method calls.
749
-
750
- Raises:
751
- AGNT5NotImplementedError: Entities are Phase 2 (Q1 2025)
752
- """
753
- raise AGNT5NotImplementedError(
754
- "ctx.entity() will be implemented in Phase 2 - Entities (Q1 2025)"
755
- )
756
-
757
-
758
- # Placeholder classes for type hints
759
- class MetricsClient:
760
- """Placeholder for metrics client."""
761
-
762
- pass
763
-
764
-
765
- class TraceSpan:
766
- """Placeholder for trace span."""
767
-
768
- pass
769
-
770
-
771
- class LLMClient:
772
- """Placeholder for LLM client."""
773
-
774
- pass
775
-
776
-
777
- class ToolsClient:
778
- """Placeholder for tools client."""
779
-
780
- pass
781
-
782
-
783
- class MemoryClient:
784
- """Placeholder for memory client."""
785
-
786
- pass
787
-
788
-
789
- class SecretsClient:
790
- """Placeholder for secrets client."""
791
-
792
- pass
793
-
794
-
795
- class ConfigClient:
796
- """Placeholder for config client."""
797
-
798
- pass
799
-
800
83
 
801
- class EntityProxy:
802
- """Placeholder for entity proxy."""
803
84
 
804
- pass