abstractagent 0.3.0__py3-none-any.whl → 0.3.1__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.
- abstractagent/adapters/codeact_runtime.py +86 -5
- abstractagent/adapters/generation_params.py +82 -0
- abstractagent/adapters/media.py +45 -0
- abstractagent/adapters/memact_runtime.py +248 -10
- abstractagent/adapters/react_runtime.py +1092 -917
- abstractagent/agents/base.py +31 -0
- abstractagent/agents/codeact.py +36 -0
- abstractagent/agents/memact.py +37 -1
- abstractagent/agents/react.py +44 -6
- abstractagent/logic/builtins.py +58 -0
- abstractagent/logic/codeact.py +4 -1
- abstractagent/logic/memact.py +2 -1
- abstractagent/logic/react.py +37 -56
- abstractagent/tools/__init__.py +6 -0
- abstractagent-0.3.1.dist-info/METADATA +112 -0
- abstractagent-0.3.1.dist-info/RECORD +33 -0
- {abstractagent-0.3.0.dist-info → abstractagent-0.3.1.dist-info}/WHEEL +1 -1
- abstractagent-0.3.0.dist-info/METADATA +0 -133
- abstractagent-0.3.0.dist-info/RECORD +0 -31
- {abstractagent-0.3.0.dist-info → abstractagent-0.3.1.dist-info}/entry_points.txt +0 -0
- {abstractagent-0.3.0.dist-info → abstractagent-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {abstractagent-0.3.0.dist-info → abstractagent-0.3.1.dist-info}/top_level.txt +0 -0
abstractagent/agents/base.py
CHANGED
|
@@ -49,11 +49,42 @@ class BaseAgent(ABC):
|
|
|
49
49
|
self.tools = tools or []
|
|
50
50
|
self.on_step = on_step
|
|
51
51
|
self.workflow = self._create_workflow()
|
|
52
|
+
self._ensure_workflow_registered()
|
|
52
53
|
self._current_run_id: Optional[str] = None
|
|
53
54
|
self.actor_id: Optional[str] = actor_id
|
|
54
55
|
self.session_id: Optional[str] = session_id
|
|
55
56
|
self.session_messages: List[Dict[str, Any]] = []
|
|
56
57
|
|
|
58
|
+
def _ensure_workflow_registered(self) -> None:
|
|
59
|
+
"""Ensure this agent's workflow is registered for subworkflow composition.
|
|
60
|
+
|
|
61
|
+
START_SUBWORKFLOW requires a runtime workflow_registry. Registering here keeps
|
|
62
|
+
agent-created runtimes usable for delegation/subflows without extra host wiring.
|
|
63
|
+
"""
|
|
64
|
+
try:
|
|
65
|
+
wf = getattr(self, "workflow", None)
|
|
66
|
+
wid = getattr(wf, "workflow_id", None)
|
|
67
|
+
if not isinstance(wid, str) or not wid.strip():
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
reg = getattr(self.runtime, "workflow_registry", None)
|
|
71
|
+
if reg is None:
|
|
72
|
+
from abstractruntime.scheduler.registry import WorkflowRegistry
|
|
73
|
+
|
|
74
|
+
reg = WorkflowRegistry()
|
|
75
|
+
setter = getattr(self.runtime, "set_workflow_registry", None)
|
|
76
|
+
if callable(setter):
|
|
77
|
+
setter(reg)
|
|
78
|
+
getter = getattr(reg, "get", None)
|
|
79
|
+
if callable(getter) and getter(wid) is not None:
|
|
80
|
+
return
|
|
81
|
+
register = getattr(reg, "register", None)
|
|
82
|
+
if callable(register):
|
|
83
|
+
register(wf)
|
|
84
|
+
except Exception:
|
|
85
|
+
# Never block agent creation due to registry wiring; hosts can still inject their own registry.
|
|
86
|
+
return
|
|
87
|
+
|
|
57
88
|
def _sync_session_caches_from_state(self, state: Optional[RunState]) -> None:
|
|
58
89
|
if state is None or not hasattr(state, "vars") or not isinstance(state.vars, dict):
|
|
59
90
|
return
|
abstractagent/agents/codeact.py
CHANGED
|
@@ -12,7 +12,9 @@ from ..adapters.codeact_runtime import create_codeact_workflow
|
|
|
12
12
|
from ..logic.builtins import (
|
|
13
13
|
ASK_USER_TOOL,
|
|
14
14
|
COMPACT_MEMORY_TOOL,
|
|
15
|
+
DELEGATE_AGENT_TOOL,
|
|
15
16
|
INSPECT_VARS_TOOL,
|
|
17
|
+
OPEN_ATTACHMENT_TOOL,
|
|
16
18
|
RECALL_MEMORY_TOOL,
|
|
17
19
|
REMEMBER_TOOL,
|
|
18
20
|
REMEMBER_NOTE_TOOL,
|
|
@@ -85,11 +87,13 @@ class CodeActAgent(BaseAgent):
|
|
|
85
87
|
tool_defs = _tool_definitions_from_callables(self.tools)
|
|
86
88
|
tool_defs = [
|
|
87
89
|
ASK_USER_TOOL,
|
|
90
|
+
OPEN_ATTACHMENT_TOOL,
|
|
88
91
|
RECALL_MEMORY_TOOL,
|
|
89
92
|
INSPECT_VARS_TOOL,
|
|
90
93
|
REMEMBER_TOOL,
|
|
91
94
|
REMEMBER_NOTE_TOOL,
|
|
92
95
|
COMPACT_MEMORY_TOOL,
|
|
96
|
+
DELEGATE_AGENT_TOOL,
|
|
93
97
|
*tool_defs,
|
|
94
98
|
]
|
|
95
99
|
logic = CodeActLogic(
|
|
@@ -108,6 +112,9 @@ class CodeActAgent(BaseAgent):
|
|
|
108
112
|
review_mode: Optional[bool] = None,
|
|
109
113
|
review_max_rounds: Optional[int] = None,
|
|
110
114
|
allowed_tools: Optional[List[str]] = None,
|
|
115
|
+
temperature: Optional[float] = None,
|
|
116
|
+
seed: Optional[int] = None,
|
|
117
|
+
attachments: Optional[List[Any]] = None,
|
|
111
118
|
) -> str:
|
|
112
119
|
task = str(task or "").strip()
|
|
113
120
|
if not task:
|
|
@@ -158,9 +165,38 @@ class CodeActAgent(BaseAgent):
|
|
|
158
165
|
# Canonical _limits namespace for runtime awareness
|
|
159
166
|
"_limits": limits,
|
|
160
167
|
}
|
|
168
|
+
if temperature is not None:
|
|
169
|
+
try:
|
|
170
|
+
vars["_runtime"]["temperature"] = float(temperature)
|
|
171
|
+
except Exception:
|
|
172
|
+
pass
|
|
173
|
+
if seed is not None:
|
|
174
|
+
try:
|
|
175
|
+
vars["_runtime"]["seed"] = int(seed)
|
|
176
|
+
except Exception:
|
|
177
|
+
pass
|
|
161
178
|
if isinstance(allowed_tools, list):
|
|
162
179
|
normalized = [str(t).strip() for t in allowed_tools if isinstance(t, str) and t.strip()]
|
|
163
180
|
vars["_runtime"]["allowed_tools"] = normalized
|
|
181
|
+
if attachments:
|
|
182
|
+
items: list[Any]
|
|
183
|
+
if isinstance(attachments, tuple):
|
|
184
|
+
items = list(attachments)
|
|
185
|
+
else:
|
|
186
|
+
items = attachments if isinstance(attachments, list) else []
|
|
187
|
+
normalized: list[Any] = []
|
|
188
|
+
for item in items:
|
|
189
|
+
if isinstance(item, str) and item.strip():
|
|
190
|
+
normalized.append(item.strip())
|
|
191
|
+
continue
|
|
192
|
+
if isinstance(item, dict):
|
|
193
|
+
aid = item.get("$artifact")
|
|
194
|
+
if not (isinstance(aid, str) and aid.strip()):
|
|
195
|
+
aid = item.get("artifact_id")
|
|
196
|
+
if isinstance(aid, str) and aid.strip():
|
|
197
|
+
normalized.append(dict(item))
|
|
198
|
+
if normalized:
|
|
199
|
+
vars["context"]["attachments"] = normalized
|
|
164
200
|
|
|
165
201
|
run_id = self.runtime.start(
|
|
166
202
|
workflow=self.workflow,
|
abstractagent/agents/memact.py
CHANGED
|
@@ -17,7 +17,9 @@ from ..adapters.memact_runtime import create_memact_workflow
|
|
|
17
17
|
from ..logic.builtins import (
|
|
18
18
|
ASK_USER_TOOL,
|
|
19
19
|
COMPACT_MEMORY_TOOL,
|
|
20
|
+
DELEGATE_AGENT_TOOL,
|
|
20
21
|
INSPECT_VARS_TOOL,
|
|
22
|
+
OPEN_ATTACHMENT_TOOL,
|
|
21
23
|
RECALL_MEMORY_TOOL,
|
|
22
24
|
REMEMBER_TOOL,
|
|
23
25
|
REMEMBER_NOTE_TOOL,
|
|
@@ -97,11 +99,13 @@ class MemActAgent(BaseAgent):
|
|
|
97
99
|
tool_defs = _tool_definitions_from_callables(self.tools)
|
|
98
100
|
tool_defs = [
|
|
99
101
|
ASK_USER_TOOL,
|
|
102
|
+
OPEN_ATTACHMENT_TOOL,
|
|
100
103
|
RECALL_MEMORY_TOOL,
|
|
101
104
|
INSPECT_VARS_TOOL,
|
|
102
105
|
REMEMBER_TOOL,
|
|
103
106
|
REMEMBER_NOTE_TOOL,
|
|
104
107
|
COMPACT_MEMORY_TOOL,
|
|
108
|
+
DELEGATE_AGENT_TOOL,
|
|
105
109
|
*tool_defs,
|
|
106
110
|
]
|
|
107
111
|
logic = MemActLogic(
|
|
@@ -131,6 +135,9 @@ class MemActAgent(BaseAgent):
|
|
|
131
135
|
review_mode: Optional[bool] = None,
|
|
132
136
|
review_max_rounds: Optional[int] = None,
|
|
133
137
|
allowed_tools: Optional[List[str]] = None,
|
|
138
|
+
temperature: Optional[float] = None,
|
|
139
|
+
seed: Optional[int] = None,
|
|
140
|
+
attachments: Optional[List[Any]] = None,
|
|
134
141
|
) -> str:
|
|
135
142
|
task = str(task or "").strip()
|
|
136
143
|
if not task:
|
|
@@ -168,6 +175,16 @@ class MemActAgent(BaseAgent):
|
|
|
168
175
|
"review_mode": eff_review_mode,
|
|
169
176
|
"review_max_rounds": eff_review_max_rounds,
|
|
170
177
|
}
|
|
178
|
+
if temperature is not None:
|
|
179
|
+
try:
|
|
180
|
+
runtime_ns["temperature"] = float(temperature)
|
|
181
|
+
except Exception:
|
|
182
|
+
pass
|
|
183
|
+
if seed is not None:
|
|
184
|
+
try:
|
|
185
|
+
runtime_ns["seed"] = int(seed)
|
|
186
|
+
except Exception:
|
|
187
|
+
pass
|
|
171
188
|
if isinstance(self.session_active_memory, dict):
|
|
172
189
|
runtime_ns["active_memory"] = _deepcopy_json(self.session_active_memory)
|
|
173
190
|
if isinstance(allowed_tools, list):
|
|
@@ -181,6 +198,25 @@ class MemActAgent(BaseAgent):
|
|
|
181
198
|
"_temp": {},
|
|
182
199
|
"_limits": limits,
|
|
183
200
|
}
|
|
201
|
+
if attachments:
|
|
202
|
+
items: list[Any]
|
|
203
|
+
if isinstance(attachments, tuple):
|
|
204
|
+
items = list(attachments)
|
|
205
|
+
else:
|
|
206
|
+
items = attachments if isinstance(attachments, list) else []
|
|
207
|
+
normalized: list[Any] = []
|
|
208
|
+
for item in items:
|
|
209
|
+
if isinstance(item, str) and item.strip():
|
|
210
|
+
normalized.append(item.strip())
|
|
211
|
+
continue
|
|
212
|
+
if isinstance(item, dict):
|
|
213
|
+
aid = item.get("$artifact")
|
|
214
|
+
if not (isinstance(aid, str) and aid.strip()):
|
|
215
|
+
aid = item.get("artifact_id")
|
|
216
|
+
if isinstance(aid, str) and aid.strip():
|
|
217
|
+
normalized.append(dict(item))
|
|
218
|
+
if normalized:
|
|
219
|
+
vars["context"]["attachments"] = normalized
|
|
184
220
|
|
|
185
221
|
run_id = self.runtime.start(
|
|
186
222
|
workflow=self.workflow,
|
|
@@ -228,7 +264,7 @@ def create_memact_agent(
|
|
|
228
264
|
provider=provider,
|
|
229
265
|
model=model,
|
|
230
266
|
llm_kwargs=llm_kwargs,
|
|
231
|
-
|
|
267
|
+
tool_executor=MappingToolExecutor.from_tools(list(tools)),
|
|
232
268
|
run_store=run_store,
|
|
233
269
|
ledger_store=ledger_store,
|
|
234
270
|
)
|
abstractagent/agents/react.py
CHANGED
|
@@ -22,7 +22,9 @@ from ..adapters.react_runtime import create_react_workflow
|
|
|
22
22
|
from ..logic.builtins import (
|
|
23
23
|
ASK_USER_TOOL,
|
|
24
24
|
COMPACT_MEMORY_TOOL,
|
|
25
|
+
DELEGATE_AGENT_TOOL,
|
|
25
26
|
INSPECT_VARS_TOOL,
|
|
27
|
+
OPEN_ATTACHMENT_TOOL,
|
|
26
28
|
RECALL_MEMORY_TOOL,
|
|
27
29
|
REMEMBER_TOOL,
|
|
28
30
|
REMEMBER_NOTE_TOOL,
|
|
@@ -96,11 +98,13 @@ class ReactAgent(BaseAgent):
|
|
|
96
98
|
# Built-in ask_user is a schema-only tool (handled via ASK_USER effect in the adapter).
|
|
97
99
|
tool_defs = [
|
|
98
100
|
ASK_USER_TOOL,
|
|
101
|
+
OPEN_ATTACHMENT_TOOL,
|
|
99
102
|
RECALL_MEMORY_TOOL,
|
|
100
103
|
INSPECT_VARS_TOOL,
|
|
101
104
|
REMEMBER_TOOL,
|
|
102
105
|
REMEMBER_NOTE_TOOL,
|
|
103
106
|
COMPACT_MEMORY_TOOL,
|
|
107
|
+
DELEGATE_AGENT_TOOL,
|
|
104
108
|
*tool_defs,
|
|
105
109
|
]
|
|
106
110
|
|
|
@@ -120,6 +124,9 @@ class ReactAgent(BaseAgent):
|
|
|
120
124
|
review_mode: Optional[bool] = None,
|
|
121
125
|
review_max_rounds: Optional[int] = None,
|
|
122
126
|
allowed_tools: Optional[List[str]] = None,
|
|
127
|
+
temperature: Optional[float] = None,
|
|
128
|
+
seed: Optional[int] = None,
|
|
129
|
+
attachments: Optional[List[Any]] = None,
|
|
123
130
|
) -> str:
|
|
124
131
|
task = str(task or "").strip()
|
|
125
132
|
if not task:
|
|
@@ -150,14 +157,16 @@ class ReactAgent(BaseAgent):
|
|
|
150
157
|
limits.setdefault("max_message_chars", -1)
|
|
151
158
|
limits.setdefault("max_tool_message_chars", -1)
|
|
152
159
|
limits["estimated_tokens_used"] = 0
|
|
160
|
+
# ReAct output-token capping is controlled via `_limits.max_output_tokens`.
|
|
161
|
+
# Policy: unset by default (None) to avoid artificial truncation.
|
|
153
162
|
try:
|
|
154
|
-
|
|
163
|
+
max_output_tokens_override = int(self._max_tokens) if self._max_tokens is not None else None
|
|
155
164
|
except Exception:
|
|
156
|
-
|
|
157
|
-
if isinstance(
|
|
158
|
-
limits["
|
|
159
|
-
|
|
160
|
-
limits
|
|
165
|
+
max_output_tokens_override = None
|
|
166
|
+
if isinstance(max_output_tokens_override, int) and max_output_tokens_override > 0:
|
|
167
|
+
limits["max_output_tokens"] = max_output_tokens_override
|
|
168
|
+
else:
|
|
169
|
+
limits.setdefault("max_output_tokens", None)
|
|
161
170
|
|
|
162
171
|
vars: Dict[str, Any] = {
|
|
163
172
|
"context": {"task": task, "messages": _copy_messages(self.session_messages)},
|
|
@@ -172,6 +181,35 @@ class ReactAgent(BaseAgent):
|
|
|
172
181
|
# Canonical _limits namespace for runtime awareness
|
|
173
182
|
"_limits": limits,
|
|
174
183
|
}
|
|
184
|
+
if attachments:
|
|
185
|
+
items: list[Any]
|
|
186
|
+
if isinstance(attachments, tuple):
|
|
187
|
+
items = list(attachments)
|
|
188
|
+
else:
|
|
189
|
+
items = attachments if isinstance(attachments, list) else []
|
|
190
|
+
normalized: list[Any] = []
|
|
191
|
+
for item in items:
|
|
192
|
+
if isinstance(item, str) and item.strip():
|
|
193
|
+
normalized.append(item.strip())
|
|
194
|
+
continue
|
|
195
|
+
if isinstance(item, dict):
|
|
196
|
+
aid = item.get("$artifact")
|
|
197
|
+
if not (isinstance(aid, str) and aid.strip()):
|
|
198
|
+
aid = item.get("artifact_id")
|
|
199
|
+
if isinstance(aid, str) and aid.strip():
|
|
200
|
+
normalized.append(dict(item))
|
|
201
|
+
if normalized:
|
|
202
|
+
vars["context"]["attachments"] = normalized
|
|
203
|
+
if temperature is not None:
|
|
204
|
+
try:
|
|
205
|
+
vars["_runtime"]["temperature"] = float(temperature)
|
|
206
|
+
except Exception:
|
|
207
|
+
pass
|
|
208
|
+
if seed is not None:
|
|
209
|
+
try:
|
|
210
|
+
vars["_runtime"]["seed"] = int(seed)
|
|
211
|
+
except Exception:
|
|
212
|
+
pass
|
|
175
213
|
if isinstance(allowed_tools, list):
|
|
176
214
|
normalized = [str(t).strip() for t in allowed_tools if isinstance(t, str) and t.strip()]
|
|
177
215
|
vars["_runtime"]["allowed_tools"] = normalized
|
abstractagent/logic/builtins.py
CHANGED
|
@@ -25,6 +25,23 @@ ASK_USER_TOOL = ToolDefinition(
|
|
|
25
25
|
when_to_use="Use when the task is ambiguous or you need user input to proceed.",
|
|
26
26
|
)
|
|
27
27
|
|
|
28
|
+
OPEN_ATTACHMENT_TOOL = ToolDefinition(
|
|
29
|
+
name="open_attachment",
|
|
30
|
+
description="Read an attachment (artifact) from the current session with bounded output.",
|
|
31
|
+
parameters={
|
|
32
|
+
"artifact_id": {"type": "string", "description": "Attachment artifact id (preferred).", "default": None},
|
|
33
|
+
"handle": {"type": "string", "description": "Attachment handle (usually '@path').", "default": None},
|
|
34
|
+
"expected_sha256": {"type": "string", "description": "Optional sha256 to disambiguate versions.", "default": None},
|
|
35
|
+
"start_line": {"type": "integer", "description": "1-based start line (default 1).", "default": 1},
|
|
36
|
+
"end_line": {"type": "integer", "description": "Optional end line (inclusive).", "default": None},
|
|
37
|
+
"max_chars": {"type": "integer", "description": "Maximum characters to return (default 8000).", "default": 8000},
|
|
38
|
+
},
|
|
39
|
+
when_to_use=(
|
|
40
|
+
"Use only to open stored (non-active) session attachments or to fetch a bounded, line-numbered excerpt when the "
|
|
41
|
+
"attachment is not already provided in this call. Prefer artifact_id when available; otherwise use handle."
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
|
|
28
45
|
RECALL_MEMORY_TOOL = ToolDefinition(
|
|
29
46
|
name="recall_memory",
|
|
30
47
|
description="Recall archived memory spans with provenance (by span_id/query/tags/time range).",
|
|
@@ -113,6 +130,18 @@ RECALL_MEMORY_TOOL = ToolDefinition(
|
|
|
113
130
|
"description": "Memory scope to query: run | session | global | all (default run).",
|
|
114
131
|
"default": "run",
|
|
115
132
|
},
|
|
133
|
+
"recall_level": {
|
|
134
|
+
"type": "string",
|
|
135
|
+
"enum": ["urgent", "standard", "deep"],
|
|
136
|
+
"description": (
|
|
137
|
+
"Optional recall effort policy.\n"
|
|
138
|
+
"- urgent: fastest, tight budgets\n"
|
|
139
|
+
"- standard: bounded but more thorough\n"
|
|
140
|
+
"- deep: may be slow/expensive\n"
|
|
141
|
+
"No silent downgrade: budgets/permissions are enforced by the runtime."
|
|
142
|
+
),
|
|
143
|
+
"default": None,
|
|
144
|
+
},
|
|
116
145
|
},
|
|
117
146
|
when_to_use="Use after compaction or when you need exact details from earlier context.",
|
|
118
147
|
)
|
|
@@ -234,3 +263,32 @@ COMPACT_MEMORY_TOOL = ToolDefinition(
|
|
|
234
263
|
},
|
|
235
264
|
when_to_use="Use when the active context is too large and you need to reduce it while keeping provenance.",
|
|
236
265
|
)
|
|
266
|
+
|
|
267
|
+
DELEGATE_AGENT_TOOL = ToolDefinition(
|
|
268
|
+
name="delegate_agent",
|
|
269
|
+
description="Delegate a subtask to a fresh agent run with smaller context and restricted tools.",
|
|
270
|
+
parameters={
|
|
271
|
+
"task": {
|
|
272
|
+
"type": "string",
|
|
273
|
+
"description": "The delegated task (required). Keep it specific and self-contained.",
|
|
274
|
+
},
|
|
275
|
+
"context": {
|
|
276
|
+
"type": "string",
|
|
277
|
+
"description": "Minimal supporting context for the delegated task (optional).",
|
|
278
|
+
"default": "",
|
|
279
|
+
},
|
|
280
|
+
"tools": {
|
|
281
|
+
"type": "array",
|
|
282
|
+
"items": {"type": "string"},
|
|
283
|
+
"description": (
|
|
284
|
+
"Tool allowlist for the delegated agent (optional). "
|
|
285
|
+
"When omitted, the delegated agent inherits the current allowlist."
|
|
286
|
+
),
|
|
287
|
+
"default": None,
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
when_to_use=(
|
|
291
|
+
"Use when you can split off a subtask that can be completed with a small context (e.g., "
|
|
292
|
+
"inspect a few files, search for specific symbols, summarize findings)."
|
|
293
|
+
),
|
|
294
|
+
)
|
abstractagent/logic/codeact.py
CHANGED
|
@@ -101,6 +101,10 @@ class CodeActLogic:
|
|
|
101
101
|
"- Be truthful: only claim actions supported by tool outputs.\n"
|
|
102
102
|
"- Be autonomous: do not ask the user for confirmation to proceed; keep going until the task is done.\n"
|
|
103
103
|
"- If you need to run code, call `execute_python` (preferred) or output a fenced ```python code block.\n"
|
|
104
|
+
"- Efficiency: batch independent read-only tool calls into a single turn (multiple tool calls) to reduce iterations.\n"
|
|
105
|
+
" Examples: read_file for multiple files/ranges, search_files with different queries, list_files across folders, analyze_code on multiple targets.\n"
|
|
106
|
+
" Only split tool calls across turns when later calls depend on earlier outputs; avoid batching side-effectful tools (write/edit/execute).\n"
|
|
107
|
+
"- When context is getting large, use delegate_agent(task, context, tools) to offload an independent subtask with minimal context.\n"
|
|
104
108
|
"- Never fabricate tool outputs.\n"
|
|
105
109
|
"- Only ask the user a question when required information is missing.\n"
|
|
106
110
|
f"{output_budget_line}"
|
|
@@ -170,4 +174,3 @@ class CodeActLogic:
|
|
|
170
174
|
if success:
|
|
171
175
|
return f"[{name}]: {out}"
|
|
172
176
|
return f"[{name}]: Error: {out}"
|
|
173
|
-
|
abstractagent/logic/memact.py
CHANGED
|
@@ -72,6 +72,8 @@ class MemActLogic:
|
|
|
72
72
|
"- Be truthful: only claim actions supported by tool outputs.\n"
|
|
73
73
|
"- Be autonomous: do not ask the user for confirmation to proceed; keep going until the task is done.\n"
|
|
74
74
|
"- If you need to create/edit files, run commands, fetch URLs, or search, you MUST call an appropriate tool.\n"
|
|
75
|
+
"- Efficiency: batch independent read-only tool calls into a single turn (multiple tool calls) when possible.\n"
|
|
76
|
+
"- When context is getting large, use delegate_agent(task, context, tools) to offload an independent subtask with minimal context.\n"
|
|
75
77
|
"- Never fabricate tool outputs.\n"
|
|
76
78
|
"- Only ask the user a question when required information is missing.\n"
|
|
77
79
|
f"{output_budget_line}"
|
|
@@ -124,4 +126,3 @@ class MemActLogic:
|
|
|
124
126
|
if success:
|
|
125
127
|
return f"[{name}]: {output}"
|
|
126
128
|
return f"[{name}]: Error: {output}"
|
|
127
|
-
|
abstractagent/logic/react.py
CHANGED
|
@@ -71,9 +71,10 @@ class ReActLogic:
|
|
|
71
71
|
- The user request belongs in the user-role message (prompt), not in the system prompt.
|
|
72
72
|
- Conversation + tool history is provided via `messages` by the runtime adapter.
|
|
73
73
|
"""
|
|
74
|
-
|
|
74
|
+
# History is carried out-of-band via `messages`; keep logic pure.
|
|
75
|
+
_ = messages
|
|
75
76
|
|
|
76
|
-
task = str(task or "")
|
|
77
|
+
task = str(task or "").strip()
|
|
77
78
|
guidance = str(guidance or "").strip()
|
|
78
79
|
|
|
79
80
|
# Output token cap (provider max_tokens) comes from `_limits.max_output_tokens`.
|
|
@@ -84,64 +85,45 @@ class ReActLogic:
|
|
|
84
85
|
max_output_tokens = int(max_output_tokens)
|
|
85
86
|
except Exception:
|
|
86
87
|
max_output_tokens = None
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
scratchpad = (vars or {}).get("scratchpad", {})
|
|
90
|
-
plan_mode = bool(runtime_ns.get("plan_mode")) if isinstance(runtime_ns, dict) else False
|
|
91
|
-
plan_text = scratchpad.get("plan") if isinstance(scratchpad, dict) else None
|
|
92
|
-
plan = str(plan_text).strip() if isinstance(plan_text, str) and plan_text.strip() else ""
|
|
93
|
-
|
|
94
|
-
prompt = task.strip()
|
|
95
|
-
|
|
96
|
-
output_budget_line = ""
|
|
97
|
-
if isinstance(max_output_tokens, int) and max_output_tokens > 0:
|
|
98
|
-
output_budget_line = (
|
|
99
|
-
f"- Output token limit for this response: {max_output_tokens}.\n"
|
|
100
|
-
)
|
|
88
|
+
if not isinstance(max_output_tokens, int) or max_output_tokens <= 0:
|
|
89
|
+
max_output_tokens = None
|
|
101
90
|
|
|
102
91
|
system_prompt = (
|
|
103
92
|
f"Iteration: {int(iteration)}/{int(max_iterations)}\n\n"
|
|
104
|
-
"
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
-
|
|
109
|
-
- If
|
|
110
|
-
|
|
111
|
-
-
|
|
112
|
-
-
|
|
113
|
-
-
|
|
114
|
-
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
-
|
|
120
|
-
-
|
|
121
|
-
""
|
|
93
|
+
"## MY PERSONA\n"
|
|
94
|
+
"You are an autonomous ReAct agent (Reason → Act → Observe).\n\n"
|
|
95
|
+
"Loop contract:\n"
|
|
96
|
+
"- THINK briefly using the full transcript and prior observations.\n"
|
|
97
|
+
"- If you need to ACT, CALL one or more tools (function calls).\n"
|
|
98
|
+
"- If you are DONE, respond with the final answer and NO tool calls.\n\n"
|
|
99
|
+
"Rules:\n"
|
|
100
|
+
"- Choose tools yourself; never ask the user which tool to run.\n"
|
|
101
|
+
"- Do not write a long plan before tool calls.\n"
|
|
102
|
+
"- Keep non-final responses short; do not draft large deliverables in chat when tools can build them.\n"
|
|
103
|
+
"- Efficiency (important): the runtime supports MULTIPLE tool calls in one response.\n"
|
|
104
|
+
" Batch independent read-only tool calls to reduce iterations.\n"
|
|
105
|
+
" Example: read multiple files/ranges or run multiple searches in one response.\n"
|
|
106
|
+
" If reading nearby ranges of the same file, prefer ONE call with a wider range.\n"
|
|
107
|
+
" Only split tool calls across turns when later calls depend on earlier outputs; do NOT batch side-effectful tools (write_file/edit_file/execute_command/send_email/send_whatsapp_message/send_telegram_message/send_telegram_artifact).\n"
|
|
108
|
+
"- When context is getting large, use delegate_agent(task, context, tools) to offload an independent subtask with minimal context.\n"
|
|
109
|
+
"- Keep tool call arguments small and valid; avoid embedding huge blobs (large file contents / giant JSON) directly in arguments.\n"
|
|
110
|
+
"- Attachments:\n"
|
|
111
|
+
" - If you see an 'Active attachments' message or inline 'Content from <file>' blocks, treat those attachments as already available in-context.\n"
|
|
112
|
+
" Do NOT call tools just to re-open/read them.\n"
|
|
113
|
+
" - If you see 'Stored session attachments', those may not be included in the current call.\n"
|
|
114
|
+
" Only if you truly need it, use the attachment-open tool with artifact_id and a bounded line range.\n"
|
|
115
|
+
" - Never use filesystem tools on attachment filenames/paths or absolute paths outside the workspace.\n"
|
|
116
|
+
"- For fetch_url: use include_full_content=False for shorter previews; set keep_links=False to strip links when not needed.\n"
|
|
117
|
+
"- For large files, create a small skeleton first, then refine via multiple smaller edits/tool calls.\n"
|
|
118
|
+
"- Use tool outputs as evidence; do not claim actions without tool outputs.\n"
|
|
119
|
+
"- Continue iterating until the task is complete.\n"
|
|
120
|
+
).strip()
|
|
122
121
|
|
|
123
122
|
if guidance:
|
|
124
|
-
system_prompt =
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if plan_mode:
|
|
130
|
-
system_prompt = (
|
|
131
|
-
system_prompt
|
|
132
|
-
+ "\n\nPlan mode:\n"
|
|
133
|
-
"- Maintain and update the plan as you work.\n"
|
|
134
|
-
"- If the plan changes, include a final section at the END of your message:\n"
|
|
135
|
-
" Plan Update:\n"
|
|
136
|
-
" <markdown checklist>\n"
|
|
137
|
-
).strip()
|
|
138
|
-
|
|
139
|
-
return LLMRequest(
|
|
140
|
-
prompt=prompt,
|
|
141
|
-
system_prompt=system_prompt,
|
|
142
|
-
tools=self.tools,
|
|
143
|
-
max_tokens=max_output_tokens,
|
|
144
|
-
)
|
|
123
|
+
system_prompt = f"{system_prompt}\n\nGuidance:\n{guidance}".strip()
|
|
124
|
+
|
|
125
|
+
# Note: prompt is unused by the runtime adapter (we supply chat `messages`).
|
|
126
|
+
return LLMRequest(prompt=task, system_prompt=system_prompt, tools=self.tools, max_tokens=max_output_tokens)
|
|
145
127
|
|
|
146
128
|
def parse_response(self, response: Any) -> Tuple[str, List[ToolCall]]:
|
|
147
129
|
if not isinstance(response, dict):
|
|
@@ -183,4 +165,3 @@ I am a truthful and collaborative autonomous ReAct agent powered by the Abstract
|
|
|
183
165
|
if success:
|
|
184
166
|
return f"[{name}]: {output}"
|
|
185
167
|
return f"[{name}]: Error: {output}"
|
|
186
|
-
|
abstractagent/tools/__init__.py
CHANGED
|
@@ -7,8 +7,10 @@ Agent-specific tools (execute_python, self_improve) are defined locally.
|
|
|
7
7
|
# Import common tools from AbstractCore (canonical source)
|
|
8
8
|
from abstractcore.tools.common_tools import (
|
|
9
9
|
list_files,
|
|
10
|
+
skim_folders,
|
|
10
11
|
analyze_code,
|
|
11
12
|
read_file,
|
|
13
|
+
skim_files,
|
|
12
14
|
search_files,
|
|
13
15
|
write_file,
|
|
14
16
|
edit_file,
|
|
@@ -25,8 +27,10 @@ from .self_improve import self_improve
|
|
|
25
27
|
ALL_TOOLS = [
|
|
26
28
|
# File operations (from abstractcore)
|
|
27
29
|
list_files,
|
|
30
|
+
skim_folders,
|
|
28
31
|
analyze_code,
|
|
29
32
|
read_file,
|
|
33
|
+
skim_files,
|
|
30
34
|
search_files,
|
|
31
35
|
write_file,
|
|
32
36
|
edit_file,
|
|
@@ -43,8 +47,10 @@ ALL_TOOLS = [
|
|
|
43
47
|
__all__ = [
|
|
44
48
|
# File operations
|
|
45
49
|
"list_files",
|
|
50
|
+
"skim_folders",
|
|
46
51
|
"analyze_code",
|
|
47
52
|
"read_file",
|
|
53
|
+
"skim_files",
|
|
48
54
|
"search_files",
|
|
49
55
|
"write_file",
|
|
50
56
|
"edit_file",
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: abstractagent
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: Agent implementations using AbstractRuntime and AbstractCore
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: abstractcore[tools]
|
|
9
|
+
Requires-Dist: abstractruntime
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
12
|
+
Dynamic: license-file
|
|
13
|
+
|
|
14
|
+
# AbstractAgent
|
|
15
|
+
|
|
16
|
+
Agent patterns (ReAct / CodeAct / MemAct) built on **AbstractRuntime** (durable execution) and **AbstractCore** (tools + LLM integration).
|
|
17
|
+
|
|
18
|
+
Start here: [`docs/getting-started.md`](docs/getting-started.md) (then [`docs/README.md`](docs/README.md) for the full index)
|
|
19
|
+
|
|
20
|
+
## Documentation
|
|
21
|
+
|
|
22
|
+
- Getting started: [`docs/getting-started.md`](docs/getting-started.md)
|
|
23
|
+
- API reference: [`docs/api.md`](docs/api.md)
|
|
24
|
+
- FAQ / troubleshooting: [`docs/faq.md`](docs/faq.md)
|
|
25
|
+
- Architecture (diagrams): [`docs/architecture.md`](docs/architecture.md)
|
|
26
|
+
- Changelog: [`CHANGELOG.md`](CHANGELOG.md)
|
|
27
|
+
- Contributing: [`CONTRIBUTING.md`](CONTRIBUTING.md)
|
|
28
|
+
- Security: [`SECURITY.md`](SECURITY.md)
|
|
29
|
+
- Acknowledgements: [`ACKNOWLEDMENTS.md`](ACKNOWLEDMENTS.md)
|
|
30
|
+
|
|
31
|
+
## What you get
|
|
32
|
+
|
|
33
|
+
- **ReAct**: tool-first Reason → Act → Observe loop
|
|
34
|
+
- **CodeAct**: executes Python (tool call or fenced ` ```python``` ` blocks)
|
|
35
|
+
- **MemAct**: memory-enhanced agent using runtime-owned Active Memory
|
|
36
|
+
- **Durable runs**: pause/resume via `run_id` + runtime stores
|
|
37
|
+
- **Tool control**: explicit tool bundles + per-run allowlists
|
|
38
|
+
- **Observability**: durable ledger of LLM calls, tool calls, and waits
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
From source (development):
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install -e .
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
With dev dependencies:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install -e ".[dev]"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
From PyPI:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install abstractagent
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Note: the repository may be ahead of the latest published PyPI release. To verify what you installed:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
python -c "import importlib.metadata as md; print(md.version('abstractagent'))"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Quick start (ReAct)
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from abstractagent import create_react_agent
|
|
70
|
+
|
|
71
|
+
agent = create_react_agent(provider="ollama", model="qwen3:1.7b-q4_K_M")
|
|
72
|
+
agent.start("List the files in the current directory")
|
|
73
|
+
state = agent.run_to_completion()
|
|
74
|
+
print(state.output["answer"])
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Persistence (resume across restarts)
|
|
78
|
+
|
|
79
|
+
By default, the factory helpers use an in-memory runtime store. For resume across process restarts,
|
|
80
|
+
pass a persistent `RunStore`/`LedgerStore` (example below uses JSON files).
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from abstractagent import create_react_agent
|
|
84
|
+
from abstractruntime.storage.json_files import JsonFileRunStore, JsonlLedgerStore
|
|
85
|
+
|
|
86
|
+
run_store = JsonFileRunStore(".runs")
|
|
87
|
+
ledger_store = JsonlLedgerStore(".runs")
|
|
88
|
+
|
|
89
|
+
agent = create_react_agent(run_store=run_store, ledger_store=ledger_store)
|
|
90
|
+
agent.start("Long running task")
|
|
91
|
+
agent.save_state("agent_state.json")
|
|
92
|
+
|
|
93
|
+
# ... later / after restart ...
|
|
94
|
+
|
|
95
|
+
agent2 = create_react_agent(run_store=run_store, ledger_store=ledger_store)
|
|
96
|
+
agent2.load_state("agent_state.json")
|
|
97
|
+
state = agent2.run_to_completion()
|
|
98
|
+
print(state.output["answer"])
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
More details: [`docs/persistence.md`](docs/persistence.md)
|
|
102
|
+
|
|
103
|
+
## CLI
|
|
104
|
+
|
|
105
|
+
This repository still installs a `react-agent` entrypoint, but it is **deprecated** and only prints a migration hint
|
|
106
|
+
(see `src/abstractagent/repl.py` and `pyproject.toml`).
|
|
107
|
+
|
|
108
|
+
Interactive UX lives in **AbstractCode**.
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
MIT (see `LICENSE`).
|