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,230 @@
1
+ """Session compaction for context window management."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass
5
+ from typing import Any
6
+
7
+ from ..llm import LLMProvider, LLMMessage
8
+
9
+
10
+ @dataclass
11
+ class CompactionConfig:
12
+ """Compaction configuration."""
13
+ # Protect recent tool outputs (tokens)
14
+ prune_protect: int = 40_000
15
+ # Minimum tokens to trigger prune
16
+ prune_minimum: int = 20_000
17
+ # Context limit threshold (0-1, fraction of model limit)
18
+ context_threshold: float = 0.8
19
+ # Reserve for output
20
+ output_reserve: int = 32_000
21
+
22
+
23
+ class SessionCompaction:
24
+ """Handle context compaction for long conversations.
25
+
26
+ Implements two strategies:
27
+ 1. Prune: Remove old tool outputs beyond protection window
28
+ 2. Summarize: Use LLM to compress conversation history
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ llm: LLMProvider,
34
+ config: CompactionConfig | None = None,
35
+ ):
36
+ self._llm = llm
37
+ self._config = config or CompactionConfig()
38
+
39
+ def estimate_tokens(self, text: str | dict | list) -> int:
40
+ """Estimate token count for content.
41
+
42
+ Simple heuristic: ~4 characters per token.
43
+ """
44
+ import json
45
+
46
+ if isinstance(text, (dict, list)):
47
+ text = json.dumps(text, ensure_ascii=False)
48
+
49
+ return len(text) // 4
50
+
51
+ def estimate_messages_tokens(self, messages: list[LLMMessage]) -> int:
52
+ """Estimate total tokens in message list."""
53
+ total = 0
54
+ for msg in messages:
55
+ if isinstance(msg.content, str):
56
+ total += self.estimate_tokens(msg.content)
57
+ else:
58
+ total += self.estimate_tokens(msg.content)
59
+ return total
60
+
61
+ async def is_overflow(
62
+ self,
63
+ messages: list[LLMMessage],
64
+ context_limit: int,
65
+ ) -> bool:
66
+ """Check if context needs compaction."""
67
+ total_tokens = self.estimate_messages_tokens(messages)
68
+ usable = int(context_limit * self._config.context_threshold) - self._config.output_reserve
69
+ return total_tokens > usable
70
+
71
+ async def prune(
72
+ self,
73
+ messages: list[LLMMessage],
74
+ ) -> tuple[list[LLMMessage], list[LLMMessage]]:
75
+ """Prune old tool outputs.
76
+
77
+ Keeps recent tool outputs within prune_protect window.
78
+ Returns (pruned_messages, ejected_messages).
79
+ """
80
+ # Find tool result messages
81
+ tool_results = []
82
+ for i, msg in enumerate(messages):
83
+ content = msg.content
84
+ if isinstance(content, list):
85
+ for part in content:
86
+ if isinstance(part, dict) and part.get("type") == "tool_result":
87
+ tokens = self.estimate_tokens(part.get("content", ""))
88
+ tool_results.append((i, part, tokens))
89
+
90
+ # Calculate which to prune (from oldest)
91
+ total_tokens = 0
92
+ to_prune = []
93
+
94
+ # Process in reverse (newest first)
95
+ for item in reversed(tool_results):
96
+ total_tokens += item[2]
97
+ if total_tokens > self._config.prune_protect:
98
+ to_prune.append(item)
99
+
100
+ # Check minimum threshold
101
+ prune_tokens = sum(t[2] for t in to_prune)
102
+ if prune_tokens < self._config.prune_minimum:
103
+ return messages, []
104
+
105
+ # Create pruned messages
106
+ pruned = []
107
+ ejected = []
108
+ prune_indices = {t[0] for t in to_prune}
109
+
110
+ for i, msg in enumerate(messages):
111
+ if i in prune_indices:
112
+ # Replace tool result content with placeholder
113
+ if isinstance(msg.content, list):
114
+ new_content = []
115
+ for part in msg.content:
116
+ if isinstance(part, dict) and part.get("type") == "tool_result":
117
+ # Store original for ejection
118
+ ejected.append(LLMMessage(
119
+ role=msg.role,
120
+ content=[part],
121
+ ))
122
+ # Replace with placeholder
123
+ new_content.append({
124
+ **part,
125
+ "content": "[Old tool result content cleared]",
126
+ })
127
+ else:
128
+ new_content.append(part)
129
+ pruned.append(LLMMessage(role=msg.role, content=new_content))
130
+ else:
131
+ pruned.append(msg)
132
+ else:
133
+ pruned.append(msg)
134
+
135
+ return pruned, ejected
136
+
137
+ async def summarize(
138
+ self,
139
+ messages: list[LLMMessage],
140
+ max_summary_tokens: int = 4096,
141
+ ) -> str:
142
+ """Generate summary of conversation history."""
143
+ import json
144
+
145
+ # Build summary prompt
146
+ prompt = self._build_compaction_prompt(messages)
147
+
148
+ # Call LLM for summary
149
+ summary_messages = [LLMMessage(role="user", content=prompt)]
150
+
151
+ result_text = ""
152
+ async for event in self._llm.complete(
153
+ messages=summary_messages,
154
+ max_tokens=max_summary_tokens,
155
+ ):
156
+ if event.type == "content" and event.delta:
157
+ result_text += event.delta
158
+
159
+ return result_text
160
+
161
+ def _build_compaction_prompt(self, messages: list[LLMMessage]) -> str:
162
+ """Build prompt for summarization."""
163
+ import json
164
+
165
+ # Format messages
166
+ formatted = []
167
+ for msg in messages:
168
+ if isinstance(msg.content, str):
169
+ formatted.append(f"[{msg.role}]: {msg.content}")
170
+ else:
171
+ formatted.append(f"[{msg.role}]: {json.dumps(msg.content, ensure_ascii=False)}")
172
+
173
+ conversation = "\n\n".join(formatted)
174
+
175
+ return f"""Please summarize the following conversation history, preserving:
176
+ - Key decisions and outcomes
177
+ - Important context and facts
178
+ - Tool execution results (summarized)
179
+ - User preferences and requirements
180
+
181
+ Conversation:
182
+ {conversation}
183
+
184
+ Summary:"""
185
+
186
+ async def compact(
187
+ self,
188
+ messages: list[LLMMessage],
189
+ context_limit: int,
190
+ ) -> tuple[list[LLMMessage], dict[str, Any]]:
191
+ """Full compaction: prune then summarize if needed.
192
+
193
+ Returns:
194
+ Tuple of (compacted_messages, compaction_info)
195
+ """
196
+ info = {
197
+ "original_tokens": self.estimate_messages_tokens(messages),
198
+ "pruned": False,
199
+ "summarized": False,
200
+ "ejected_count": 0,
201
+ }
202
+
203
+ # First try pruning
204
+ pruned, ejected = await self.prune(messages)
205
+ if ejected:
206
+ info["pruned"] = True
207
+ info["ejected_count"] = len(ejected)
208
+ messages = pruned
209
+
210
+ # Check if still over limit
211
+ if await self.is_overflow(messages, context_limit):
212
+ # Summarize older messages
213
+ mid_point = len(messages) // 2
214
+ old_messages = messages[:mid_point]
215
+ recent_messages = messages[mid_point:]
216
+
217
+ summary = await self.summarize(old_messages)
218
+
219
+ # Replace old messages with summary
220
+ summary_message = LLMMessage(
221
+ role="system",
222
+ content=f"[Conversation Summary]\n{summary}\n\n[End Summary]",
223
+ )
224
+
225
+ messages = [messages[0], summary_message] + recent_messages
226
+ info["summarized"] = True
227
+
228
+ info["final_tokens"] = self.estimate_messages_tokens(messages)
229
+
230
+ return messages, info
@@ -0,0 +1,87 @@
1
+ """HITL (Human-in-the-Loop) exceptions and signals.
2
+
3
+ These control agent execution flow when human input is needed.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ from dataclasses import dataclass, field
8
+ from typing import Any
9
+
10
+ # Re-export from core.signals
11
+ from ..core.signals import HITLSuspend, SuspendSignal
12
+
13
+
14
+ class HITLTimeoutError(Exception):
15
+ """Raised when HITL request times out."""
16
+
17
+ def __init__(self, request_id: str, timeout: float):
18
+ self.request_id = request_id
19
+ self.timeout = timeout
20
+ super().__init__(f"HITL request {request_id} timed out after {timeout}s")
21
+
22
+
23
+ class HITLCancelledError(Exception):
24
+ """Raised when HITL request is cancelled."""
25
+
26
+ def __init__(self, request_id: str, reason: str = "cancelled"):
27
+ self.request_id = request_id
28
+ self.reason = reason
29
+ super().__init__(f"HITL request {request_id} cancelled: {reason}")
30
+
31
+
32
+ @dataclass
33
+ class HITLRequest:
34
+ """A pending HITL request.
35
+
36
+ Stored in invocation for persistence.
37
+ """
38
+ request_id: str
39
+ request_type: str # ask_user, permission, form, workflow_human
40
+
41
+ # Display
42
+ message: str | None = None
43
+ options: list[str] | None = None
44
+
45
+ # Context
46
+ tool_name: str | None = None # If triggered by tool
47
+ node_id: str | None = None # If triggered by workflow node
48
+
49
+ # Metadata
50
+ metadata: dict[str, Any] = field(default_factory=dict)
51
+
52
+ def to_dict(self) -> dict[str, Any]:
53
+ """Convert to dictionary for serialization."""
54
+ return {
55
+ "request_id": self.request_id,
56
+ "request_type": self.request_type,
57
+ "message": self.message,
58
+ "options": self.options,
59
+ "tool_name": self.tool_name,
60
+ "node_id": self.node_id,
61
+ "metadata": self.metadata,
62
+ }
63
+
64
+ @classmethod
65
+ def from_dict(cls, data: dict[str, Any]) -> "HITLRequest":
66
+ """Create from dictionary."""
67
+ return cls(
68
+ request_id=data["request_id"],
69
+ request_type=data.get("request_type", "ask_user"),
70
+ message=data.get("message"),
71
+ options=data.get("options"),
72
+ tool_name=data.get("tool_name"),
73
+ node_id=data.get("node_id"),
74
+ metadata=data.get("metadata", {}),
75
+ )
76
+
77
+
78
+ __all__ = [
79
+ # Signals
80
+ "SuspendSignal",
81
+ "HITLSuspend",
82
+ # Exceptions
83
+ "HITLTimeoutError",
84
+ "HITLCancelledError",
85
+ # Types
86
+ "HITLRequest",
87
+ ]