aury-agent 0.0.4__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 (149) hide show
  1. aury/__init__.py +2 -0
  2. aury/agents/__init__.py +55 -0
  3. aury/agents/a2a/__init__.py +168 -0
  4. aury/agents/backends/__init__.py +196 -0
  5. aury/agents/backends/artifact/__init__.py +9 -0
  6. aury/agents/backends/artifact/memory.py +130 -0
  7. aury/agents/backends/artifact/types.py +133 -0
  8. aury/agents/backends/code/__init__.py +65 -0
  9. aury/agents/backends/file/__init__.py +11 -0
  10. aury/agents/backends/file/local.py +66 -0
  11. aury/agents/backends/file/types.py +40 -0
  12. aury/agents/backends/invocation/__init__.py +8 -0
  13. aury/agents/backends/invocation/memory.py +81 -0
  14. aury/agents/backends/invocation/types.py +110 -0
  15. aury/agents/backends/memory/__init__.py +8 -0
  16. aury/agents/backends/memory/memory.py +179 -0
  17. aury/agents/backends/memory/types.py +136 -0
  18. aury/agents/backends/message/__init__.py +9 -0
  19. aury/agents/backends/message/memory.py +122 -0
  20. aury/agents/backends/message/types.py +124 -0
  21. aury/agents/backends/sandbox.py +275 -0
  22. aury/agents/backends/session/__init__.py +8 -0
  23. aury/agents/backends/session/memory.py +93 -0
  24. aury/agents/backends/session/types.py +124 -0
  25. aury/agents/backends/shell/__init__.py +11 -0
  26. aury/agents/backends/shell/local.py +110 -0
  27. aury/agents/backends/shell/types.py +55 -0
  28. aury/agents/backends/shell.py +209 -0
  29. aury/agents/backends/snapshot/__init__.py +19 -0
  30. aury/agents/backends/snapshot/git.py +95 -0
  31. aury/agents/backends/snapshot/hybrid.py +125 -0
  32. aury/agents/backends/snapshot/memory.py +86 -0
  33. aury/agents/backends/snapshot/types.py +59 -0
  34. aury/agents/backends/state/__init__.py +29 -0
  35. aury/agents/backends/state/composite.py +49 -0
  36. aury/agents/backends/state/file.py +57 -0
  37. aury/agents/backends/state/memory.py +52 -0
  38. aury/agents/backends/state/sqlite.py +262 -0
  39. aury/agents/backends/state/types.py +178 -0
  40. aury/agents/backends/subagent/__init__.py +165 -0
  41. aury/agents/cli/__init__.py +41 -0
  42. aury/agents/cli/chat.py +239 -0
  43. aury/agents/cli/config.py +236 -0
  44. aury/agents/cli/extensions.py +460 -0
  45. aury/agents/cli/main.py +189 -0
  46. aury/agents/cli/session.py +337 -0
  47. aury/agents/cli/workflow.py +276 -0
  48. aury/agents/context_providers/__init__.py +66 -0
  49. aury/agents/context_providers/artifact.py +299 -0
  50. aury/agents/context_providers/base.py +177 -0
  51. aury/agents/context_providers/memory.py +70 -0
  52. aury/agents/context_providers/message.py +130 -0
  53. aury/agents/context_providers/skill.py +50 -0
  54. aury/agents/context_providers/subagent.py +46 -0
  55. aury/agents/context_providers/tool.py +68 -0
  56. aury/agents/core/__init__.py +83 -0
  57. aury/agents/core/base.py +573 -0
  58. aury/agents/core/context.py +797 -0
  59. aury/agents/core/context_builder.py +303 -0
  60. aury/agents/core/event_bus/__init__.py +15 -0
  61. aury/agents/core/event_bus/bus.py +203 -0
  62. aury/agents/core/factory.py +169 -0
  63. aury/agents/core/isolator.py +97 -0
  64. aury/agents/core/logging.py +95 -0
  65. aury/agents/core/parallel.py +194 -0
  66. aury/agents/core/runner.py +139 -0
  67. aury/agents/core/services/__init__.py +5 -0
  68. aury/agents/core/services/file_session.py +144 -0
  69. aury/agents/core/services/message.py +53 -0
  70. aury/agents/core/services/session.py +53 -0
  71. aury/agents/core/signals.py +109 -0
  72. aury/agents/core/state.py +363 -0
  73. aury/agents/core/types/__init__.py +107 -0
  74. aury/agents/core/types/action.py +176 -0
  75. aury/agents/core/types/artifact.py +135 -0
  76. aury/agents/core/types/block.py +736 -0
  77. aury/agents/core/types/message.py +350 -0
  78. aury/agents/core/types/recall.py +144 -0
  79. aury/agents/core/types/session.py +257 -0
  80. aury/agents/core/types/subagent.py +154 -0
  81. aury/agents/core/types/tool.py +205 -0
  82. aury/agents/eval/__init__.py +331 -0
  83. aury/agents/hitl/__init__.py +57 -0
  84. aury/agents/hitl/ask_user.py +242 -0
  85. aury/agents/hitl/compaction.py +230 -0
  86. aury/agents/hitl/exceptions.py +87 -0
  87. aury/agents/hitl/permission.py +617 -0
  88. aury/agents/hitl/revert.py +216 -0
  89. aury/agents/llm/__init__.py +31 -0
  90. aury/agents/llm/adapter.py +367 -0
  91. aury/agents/llm/openai.py +294 -0
  92. aury/agents/llm/provider.py +476 -0
  93. aury/agents/mcp/__init__.py +153 -0
  94. aury/agents/memory/__init__.py +46 -0
  95. aury/agents/memory/compaction.py +394 -0
  96. aury/agents/memory/manager.py +465 -0
  97. aury/agents/memory/processor.py +177 -0
  98. aury/agents/memory/store.py +187 -0
  99. aury/agents/memory/types.py +137 -0
  100. aury/agents/messages/__init__.py +40 -0
  101. aury/agents/messages/config.py +47 -0
  102. aury/agents/messages/raw_store.py +224 -0
  103. aury/agents/messages/store.py +118 -0
  104. aury/agents/messages/types.py +88 -0
  105. aury/agents/middleware/__init__.py +31 -0
  106. aury/agents/middleware/base.py +341 -0
  107. aury/agents/middleware/chain.py +342 -0
  108. aury/agents/middleware/message.py +129 -0
  109. aury/agents/middleware/message_container.py +126 -0
  110. aury/agents/middleware/raw_message.py +153 -0
  111. aury/agents/middleware/truncation.py +139 -0
  112. aury/agents/middleware/types.py +81 -0
  113. aury/agents/plugin.py +162 -0
  114. aury/agents/react/__init__.py +4 -0
  115. aury/agents/react/agent.py +1923 -0
  116. aury/agents/sandbox/__init__.py +23 -0
  117. aury/agents/sandbox/local.py +239 -0
  118. aury/agents/sandbox/remote.py +200 -0
  119. aury/agents/sandbox/types.py +115 -0
  120. aury/agents/skill/__init__.py +16 -0
  121. aury/agents/skill/loader.py +180 -0
  122. aury/agents/skill/types.py +83 -0
  123. aury/agents/tool/__init__.py +39 -0
  124. aury/agents/tool/builtin/__init__.py +23 -0
  125. aury/agents/tool/builtin/ask_user.py +155 -0
  126. aury/agents/tool/builtin/bash.py +107 -0
  127. aury/agents/tool/builtin/delegate.py +726 -0
  128. aury/agents/tool/builtin/edit.py +121 -0
  129. aury/agents/tool/builtin/plan.py +277 -0
  130. aury/agents/tool/builtin/read.py +91 -0
  131. aury/agents/tool/builtin/thinking.py +111 -0
  132. aury/agents/tool/builtin/yield_result.py +130 -0
  133. aury/agents/tool/decorator.py +252 -0
  134. aury/agents/tool/set.py +204 -0
  135. aury/agents/usage/__init__.py +12 -0
  136. aury/agents/usage/tracker.py +236 -0
  137. aury/agents/workflow/__init__.py +85 -0
  138. aury/agents/workflow/adapter.py +268 -0
  139. aury/agents/workflow/dag.py +116 -0
  140. aury/agents/workflow/dsl.py +575 -0
  141. aury/agents/workflow/executor.py +659 -0
  142. aury/agents/workflow/expression.py +136 -0
  143. aury/agents/workflow/parser.py +182 -0
  144. aury/agents/workflow/state.py +145 -0
  145. aury/agents/workflow/types.py +86 -0
  146. aury_agent-0.0.4.dist-info/METADATA +90 -0
  147. aury_agent-0.0.4.dist-info/RECORD +149 -0
  148. aury_agent-0.0.4.dist-info/WHEEL +4 -0
  149. aury_agent-0.0.4.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,53 @@
1
+ """Message service protocol."""
2
+ from __future__ import annotations
3
+
4
+ from abc import abstractmethod
5
+ from typing import Any, Protocol, TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING:
8
+ from ..types.block import BlockEvent, PersistedBlock
9
+ from ..types.message import Message
10
+
11
+
12
+ class MessageService(Protocol):
13
+ """Protocol for message/block management.
14
+
15
+ Handles message and block persistence.
16
+ """
17
+
18
+ @abstractmethod
19
+ async def append(
20
+ self,
21
+ session_id: str,
22
+ invocation_id: str,
23
+ block: "BlockEvent",
24
+ ) -> "PersistedBlock":
25
+ """Append a block to the message history."""
26
+ ...
27
+
28
+ @abstractmethod
29
+ async def list_blocks(
30
+ self,
31
+ session_id: str,
32
+ invocation_id: str | None = None,
33
+ limit: int = 100,
34
+ ) -> list["PersistedBlock"]:
35
+ """List blocks for a session/invocation."""
36
+ ...
37
+
38
+ @abstractmethod
39
+ async def update_block(
40
+ self,
41
+ block_id: str,
42
+ data: dict[str, Any],
43
+ ) -> None:
44
+ """Update a block's data."""
45
+ ...
46
+
47
+ @abstractmethod
48
+ async def get_block(self, block_id: str) -> "PersistedBlock | None":
49
+ """Get a specific block."""
50
+ ...
51
+
52
+
53
+ __all__ = ["MessageService"]
@@ -0,0 +1,53 @@
1
+ """Session service protocol."""
2
+ from __future__ import annotations
3
+
4
+ from abc import abstractmethod
5
+ from typing import Protocol, TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING:
8
+ from ..types.session import Session, ControlFrame
9
+
10
+
11
+ class SessionService(Protocol):
12
+ """Protocol for session management.
13
+
14
+ Handles session CRUD and control stack operations.
15
+ """
16
+
17
+ @abstractmethod
18
+ async def create(self, root_agent_id: str, **kwargs) -> "Session":
19
+ """Create a new session."""
20
+ ...
21
+
22
+ @abstractmethod
23
+ async def get(self, session_id: str) -> "Session | None":
24
+ """Get session by ID."""
25
+ ...
26
+
27
+ @abstractmethod
28
+ async def update(self, session: "Session") -> None:
29
+ """Update session."""
30
+ ...
31
+
32
+ @abstractmethod
33
+ async def delete(self, session_id: str) -> bool:
34
+ """Delete session."""
35
+ ...
36
+
37
+ @abstractmethod
38
+ async def list(self, limit: int = 100, offset: int = 0) -> list["Session"]:
39
+ """List sessions."""
40
+ ...
41
+
42
+ @abstractmethod
43
+ async def push_control(self, session_id: str, frame: "ControlFrame") -> None:
44
+ """Push control frame to session's control stack."""
45
+ ...
46
+
47
+ @abstractmethod
48
+ async def pop_control(self, session_id: str) -> "ControlFrame | None":
49
+ """Pop control frame from session's control stack."""
50
+ ...
51
+
52
+
53
+ __all__ = ["SessionService"]
@@ -0,0 +1,109 @@
1
+ """Control flow signals for agent execution.
2
+
3
+ These are NOT exceptions - they are control flow signals that inherit from
4
+ BaseException to avoid being caught by generic `except Exception` handlers.
5
+
6
+ Similar to KeyboardInterrupt and SystemExit, these signals control execution
7
+ flow rather than indicate errors.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass, field
12
+ from typing import Any
13
+
14
+
15
+ class SuspendSignal(BaseException):
16
+ """Base class for suspension signals.
17
+
18
+ Signals that execution should be suspended (not failed).
19
+ Inherits from BaseException so `except Exception` won't catch it.
20
+
21
+ Usage:
22
+ try:
23
+ await agent.run()
24
+ except SuspendSignal as s:
25
+ # Handle suspension (HITL, pause, etc.)
26
+ handle_suspend(s)
27
+ except Exception as e:
28
+ # Handle actual errors
29
+ handle_error(e)
30
+ """
31
+ pass
32
+
33
+
34
+ @dataclass
35
+ class HITLSuspend(SuspendSignal):
36
+ """Signal for Human-in-the-Loop suspension.
37
+
38
+ Raised when agent needs human input to continue.
39
+ Contains all information needed to:
40
+ 1. Display the request to user
41
+ 2. Resume execution after user responds
42
+
43
+ Attributes:
44
+ request_id: Unique ID for matching response
45
+ request_type: Type of request (ask_user, confirm, form, etc.)
46
+ message: Display message for user
47
+ options: Optional list of choices
48
+ node_id: Workflow node ID if triggered from workflow
49
+ tool_name: Tool name if triggered from tool
50
+ block_id: Associated UI block ID
51
+ metadata: Additional context
52
+ """
53
+ request_id: str
54
+ request_type: str = "ask_user"
55
+ message: str | None = None
56
+ options: list[str] | None = None
57
+ node_id: str | None = None
58
+ tool_name: str | None = None
59
+ block_id: str | None = None
60
+ metadata: dict[str, Any] = field(default_factory=dict)
61
+
62
+ def __post_init__(self):
63
+ # Initialize BaseException with a message
64
+ super().__init__(f"HITL suspend: {self.request_type} ({self.request_id})")
65
+
66
+ def to_dict(self) -> dict[str, Any]:
67
+ """Convert to dictionary for storage/transmission."""
68
+ return {
69
+ "request_id": self.request_id,
70
+ "request_type": self.request_type,
71
+ "message": self.message,
72
+ "options": self.options,
73
+ "node_id": self.node_id,
74
+ "tool_name": self.tool_name,
75
+ "block_id": self.block_id,
76
+ "metadata": self.metadata,
77
+ }
78
+
79
+ @classmethod
80
+ def from_dict(cls, data: dict[str, Any]) -> "HITLSuspend":
81
+ """Create from dictionary."""
82
+ return cls(
83
+ request_id=data["request_id"],
84
+ request_type=data.get("request_type", "ask_user"),
85
+ message=data.get("message"),
86
+ options=data.get("options"),
87
+ node_id=data.get("node_id"),
88
+ tool_name=data.get("tool_name"),
89
+ block_id=data.get("block_id"),
90
+ metadata=data.get("metadata", {}),
91
+ )
92
+
93
+
94
+ class PauseSuspend(SuspendSignal):
95
+ """Signal for manual pause (user-initiated).
96
+
97
+ Raised when user requests to pause execution.
98
+ """
99
+
100
+ def __init__(self, reason: str = "User requested pause"):
101
+ self.reason = reason
102
+ super().__init__(reason)
103
+
104
+
105
+ __all__ = [
106
+ "SuspendSignal",
107
+ "HITLSuspend",
108
+ "PauseSuspend",
109
+ ]
@@ -0,0 +1,363 @@
1
+ """State management with checkpoint support.
2
+
3
+ State provides layered storage with three partitions:
4
+ - vars: User/developer variables for prompt formatting
5
+ - data: Data flow shared between ReactAgent and WorkflowAgent
6
+ - execution: Execution state (step, message_ids, pending_request, etc.)
7
+
8
+ Supports path-based access, pattern matching, and checkpoint/restore.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ from typing import Any, TYPE_CHECKING
13
+
14
+ if TYPE_CHECKING:
15
+ from ..backends.state import StateBackend
16
+
17
+
18
+ class State:
19
+ """Layered state management with checkpoint support.
20
+
21
+ State is NOT persisted on every set() call. Instead:
22
+ - set() writes to in-memory buffer
23
+ - checkpoint() persists buffer to backend
24
+ - restore() loads from backend
25
+
26
+ This allows efficient batching and recovery from interrupts.
27
+
28
+ Example:
29
+ state = State(backend, session_id)
30
+ await state.restore() # Load existing state
31
+
32
+ state.set("vars.user_name", "高鑫")
33
+ state.set("workflow.node1_output", result)
34
+
35
+ await state.checkpoint() # Persist changes
36
+ """
37
+
38
+ def __init__(
39
+ self,
40
+ backend: "StateBackend",
41
+ session_id: str,
42
+ *,
43
+ initial_data: dict[str, Any] | None = None,
44
+ ):
45
+ """Initialize state.
46
+
47
+ Args:
48
+ backend: Storage backend for persistence
49
+ session_id: Session identifier (used as storage key)
50
+ initial_data: Optional initial state data
51
+ """
52
+ self._backend = backend
53
+ self._session_id = session_id
54
+ self._invocation_id: str | None = None
55
+ self._data: dict[str, Any] = initial_data or {
56
+ "vars": {},
57
+ "data": {},
58
+ "execution": {},
59
+ }
60
+ self._dirty = False
61
+
62
+ # ========== Partition Access ==========
63
+
64
+ @property
65
+ def vars(self) -> dict[str, Any]:
66
+ """User/developer variables (for prompt formatting).
67
+
68
+ Example:
69
+ prompt = template.format(**state.vars)
70
+ """
71
+ if "vars" not in self._data:
72
+ self._data["vars"] = {}
73
+ return self._data["vars"]
74
+
75
+ @property
76
+ def data(self) -> dict[str, Any]:
77
+ """Data flow shared between ReactAgent and WorkflowAgent.
78
+
79
+ Used for:
80
+ - Workflow node outputs
81
+ - Tool results that need to be shared
82
+ - Any data passed between agents
83
+
84
+ Example:
85
+ state.data["search_results"] = results
86
+ state.data["node1_output"] = output
87
+ """
88
+ if "data" not in self._data:
89
+ self._data["data"] = {}
90
+ return self._data["data"]
91
+
92
+ @property
93
+ def execution(self) -> dict[str, Any]:
94
+ """Execution state for current invocation.
95
+
96
+ Used for:
97
+ - step: Current step number
98
+ - message_ids: References to raw messages
99
+ - pending_request: HITL request if suspended
100
+ - current_node: Workflow current node
101
+ - completed_nodes: Workflow completed nodes
102
+
103
+ Example:
104
+ state.execution["step"] = 5
105
+ state.execution["message_ids"] = ["msg_001", "msg_002"]
106
+ """
107
+ if "execution" not in self._data:
108
+ self._data["execution"] = {}
109
+ return self._data["execution"]
110
+
111
+ # ========== Path Operations ==========
112
+
113
+ def get(self, path: str, default: Any = None) -> Any:
114
+ """Get value by path.
115
+
116
+ Args:
117
+ path: Dot-separated path (e.g., "vars.user_name")
118
+ default: Default value if path not found
119
+
120
+ Returns:
121
+ Value at path, or default
122
+
123
+ Example:
124
+ state.get("vars.user_name") # → "高鑫"
125
+ state.get("workflow.node1") # → {...}
126
+ state.get("agent.missing", 0) # → 0
127
+ """
128
+ parts = path.split(".")
129
+ current = self._data
130
+
131
+ for part in parts:
132
+ if isinstance(current, dict) and part in current:
133
+ current = current[part]
134
+ else:
135
+ return default
136
+
137
+ return current
138
+
139
+ def set(self, path: str, value: Any) -> None:
140
+ """Set value by path (writes to buffer, not persisted).
141
+
142
+ Args:
143
+ path: Dot-separated path (e.g., "vars.user_name")
144
+ value: Value to set
145
+
146
+ Example:
147
+ state.set("vars.user_name", "高鑫")
148
+ state.set("workflow.node1_output", result)
149
+ """
150
+ parts = path.split(".")
151
+ current = self._data
152
+
153
+ # Navigate to parent, creating dicts as needed
154
+ for part in parts[:-1]:
155
+ if part not in current:
156
+ current[part] = {}
157
+ current = current[part]
158
+
159
+ # Set value
160
+ current[parts[-1]] = value
161
+ self._dirty = True
162
+
163
+ def delete(self, path: str) -> bool:
164
+ """Delete value by path.
165
+
166
+ Args:
167
+ path: Dot-separated path
168
+
169
+ Returns:
170
+ True if deleted, False if not found
171
+ """
172
+ parts = path.split(".")
173
+ current = self._data
174
+
175
+ # Navigate to parent
176
+ for part in parts[:-1]:
177
+ if isinstance(current, dict) and part in current:
178
+ current = current[part]
179
+ else:
180
+ return False
181
+
182
+ # Delete
183
+ if isinstance(current, dict) and parts[-1] in current:
184
+ del current[parts[-1]]
185
+ self._dirty = True
186
+ return True
187
+
188
+ return False
189
+
190
+ def match(self, pattern: str) -> dict[str, Any]:
191
+ """Get values matching prefix pattern.
192
+
193
+ Args:
194
+ pattern: Prefix pattern ending with '*' (e.g., "workflow.*")
195
+
196
+ Returns:
197
+ Dict of matching key-value pairs (keys without prefix)
198
+
199
+ Example:
200
+ state.match("workflow.*") # → {"node1_output": ..., "node2_output": ...}
201
+ state.match("vars.*") # → {"user_name": ..., "project_name": ...}
202
+ """
203
+ if not pattern.endswith("*"):
204
+ # Exact match, return single value wrapped
205
+ val = self.get(pattern)
206
+ return {pattern.split(".")[-1]: val} if val is not None else {}
207
+
208
+ # Prefix match
209
+ prefix = pattern[:-1] # Remove '*'
210
+ if prefix.endswith("."):
211
+ prefix = prefix[:-1] # Remove trailing '.'
212
+
213
+ parent = self.get(prefix)
214
+ if isinstance(parent, dict):
215
+ return parent.copy()
216
+
217
+ return {}
218
+
219
+ def has(self, path: str) -> bool:
220
+ """Check if path exists.
221
+
222
+ Args:
223
+ path: Dot-separated path
224
+
225
+ Returns:
226
+ True if path exists
227
+ """
228
+ return self.get(path) is not None
229
+
230
+ def clear(self, partition: str | None = None) -> None:
231
+ """Clear state.
232
+
233
+ Args:
234
+ partition: If provided, clear only that partition.
235
+ If None, clear all partitions.
236
+ """
237
+ if partition:
238
+ if partition in self._data:
239
+ self._data[partition] = {}
240
+ self._dirty = True
241
+ else:
242
+ self._data = {
243
+ "vars": {},
244
+ "data": {},
245
+ "execution": {},
246
+ }
247
+ self._dirty = True
248
+
249
+ # ========== Persistence ==========
250
+
251
+ def set_invocation_id(self, invocation_id: str) -> None:
252
+ """Set current invocation ID for persistence.
253
+
254
+ State is persisted per invocation, not per session.
255
+ """
256
+ self._invocation_id = invocation_id
257
+
258
+ @property
259
+ def is_dirty(self) -> bool:
260
+ """Check if state has unsaved changes."""
261
+ return self._dirty
262
+
263
+ async def checkpoint(self) -> None:
264
+ """Persist current state to backend.
265
+
266
+ State is saved per invocation_id (not session_id).
267
+
268
+ Call at key points:
269
+ - After step completion
270
+ - After tool execution
271
+ - Before HITL suspend
272
+ """
273
+ if not self._dirty:
274
+ return
275
+
276
+ # Use invocation_id if set, otherwise fall back to session_id
277
+ key = self._invocation_id or self._session_id
278
+ await self._backend.set("state", key, self._data)
279
+ self._dirty = False
280
+
281
+ async def restore(self, invocation_id: str | None = None) -> bool:
282
+ """Restore state from backend.
283
+
284
+ Args:
285
+ invocation_id: Specific invocation to restore. If None, uses current.
286
+
287
+ Returns:
288
+ True if state was restored, False if no saved state
289
+ """
290
+ key = invocation_id or self._invocation_id or self._session_id
291
+ data = await self._backend.get("state", key)
292
+ if data:
293
+ self._data = data
294
+ # Ensure all partitions exist
295
+ for partition in ("vars", "data", "execution"):
296
+ if partition not in self._data:
297
+ self._data[partition] = {}
298
+ self._dirty = False
299
+ if invocation_id:
300
+ self._invocation_id = invocation_id
301
+ return True
302
+ return False
303
+
304
+ # ========== Utility ==========
305
+
306
+ def to_dict(self) -> dict[str, Any]:
307
+ """Export state as dict."""
308
+ return self._data.copy()
309
+
310
+ def update(self, data: dict[str, Any]) -> None:
311
+ """Bulk update state.
312
+
313
+ Args:
314
+ data: Dict with partition keys (vars, data, execution)
315
+ """
316
+ for key in ("vars", "data", "execution"):
317
+ if key in data:
318
+ self._data[key].update(data[key])
319
+ self._dirty = True
320
+
321
+ def __repr__(self) -> str:
322
+ dirty_marker = " (dirty)" if self._dirty else ""
323
+ return f"<State session={self._session_id}{dirty_marker}>"
324
+
325
+
326
+ # Convenience type for state isolation in SubAgents
327
+ class StateSnapshot:
328
+ """Immutable snapshot of state for isolation."""
329
+
330
+ def __init__(self, data: dict[str, Any]):
331
+ self._data = data
332
+
333
+ @property
334
+ def vars(self) -> dict[str, Any]:
335
+ return self._data.get("vars", {}).copy()
336
+
337
+ @property
338
+ def data(self) -> dict[str, Any]:
339
+ return self._data.get("data", {}).copy()
340
+
341
+ @property
342
+ def execution(self) -> dict[str, Any]:
343
+ return self._data.get("execution", {}).copy()
344
+
345
+ def get(self, path: str, default: Any = None) -> Any:
346
+ parts = path.split(".")
347
+ current = self._data
348
+ for part in parts:
349
+ if isinstance(current, dict) and part in current:
350
+ current = current[part]
351
+ else:
352
+ return default
353
+ return current
354
+
355
+ def to_dict(self) -> dict[str, Any]:
356
+ return {
357
+ "vars": self.vars,
358
+ "data": self.data,
359
+ "execution": self.execution,
360
+ }
361
+
362
+
363
+ __all__ = ["State", "StateSnapshot"]
@@ -0,0 +1,107 @@
1
+ """Type definitions for Aury Agent Framework."""
2
+ from .session import (
3
+ generate_id,
4
+ InvocationState,
5
+ InvocationMode,
6
+ ControlFrame,
7
+ Session,
8
+ Invocation,
9
+ )
10
+ from .subagent import (
11
+ SubAgentMode,
12
+ SubAgentInput,
13
+ SubAgentMetadata,
14
+ SubAgentResult,
15
+ )
16
+ from .block import (
17
+ BlockKind,
18
+ BlockOp,
19
+ Persistence,
20
+ ActorInfo,
21
+ BlockEvent,
22
+ PersistedBlock,
23
+ BlockHandle,
24
+ BlockAggregator,
25
+ BlockMerger,
26
+ register_merger,
27
+ get_merger,
28
+ # Helper functions
29
+ text_block,
30
+ text_delta,
31
+ thinking_block,
32
+ thinking_delta,
33
+ tool_use_block,
34
+ tool_use_patch,
35
+ tool_result_block,
36
+ error_block,
37
+ )
38
+ from .message import (
39
+ MessageRole,
40
+ Message,
41
+ PromptInput,
42
+ )
43
+ from .tool import (
44
+ ToolInfo,
45
+ ToolContext,
46
+ ToolResult,
47
+ ToolInvocationState,
48
+ ToolInvocation,
49
+ BaseTool,
50
+ ToolConfig,
51
+ )
52
+ from .action import (
53
+ ActionType,
54
+ ActionEvent,
55
+ ActionCollector,
56
+ )
57
+
58
+ __all__ = [
59
+ # Session
60
+ "generate_id",
61
+ "InvocationState",
62
+ "InvocationMode",
63
+ "ControlFrame",
64
+ "Session",
65
+ "Invocation",
66
+ # SubAgent
67
+ "SubAgentMode",
68
+ "SubAgentInput",
69
+ "SubAgentMetadata",
70
+ "SubAgentResult",
71
+ # Block
72
+ "BlockKind",
73
+ "BlockOp",
74
+ "Persistence",
75
+ "ActorInfo",
76
+ "BlockEvent",
77
+ "PersistedBlock",
78
+ "BlockHandle",
79
+ "BlockAggregator",
80
+ "BlockMerger",
81
+ "register_merger",
82
+ "get_merger",
83
+ "text_block",
84
+ "text_delta",
85
+ "thinking_block",
86
+ "thinking_delta",
87
+ "tool_use_block",
88
+ "tool_use_patch",
89
+ "tool_result_block",
90
+ "error_block",
91
+ # Message
92
+ "MessageRole",
93
+ "Message",
94
+ "PromptInput",
95
+ # Tool
96
+ "ToolInfo",
97
+ "ToolContext",
98
+ "ToolResult",
99
+ "ToolInvocationState",
100
+ "ToolInvocation",
101
+ "BaseTool",
102
+ "ToolConfig",
103
+ # Action
104
+ "ActionType",
105
+ "ActionEvent",
106
+ "ActionCollector",
107
+ ]