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.
@@ -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
@@ -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,
@@ -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
- tools=MappingToolExecutor.from_tools(tools),
267
+ tool_executor=MappingToolExecutor.from_tools(list(tools)),
232
268
  run_store=run_store,
233
269
  ledger_store=ledger_store,
234
270
  )
@@ -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
- max_tokens_override = int(self._max_tokens) if self._max_tokens is not None else None
163
+ max_output_tokens_override = int(self._max_tokens) if self._max_tokens is not None else None
155
164
  except Exception:
156
- max_tokens_override = None
157
- if isinstance(max_tokens_override, int) and max_tokens_override > 0:
158
- limits["max_tokens"] = max_tokens_override
159
- if not isinstance(limits.get("max_tokens"), int) or int(limits.get("max_tokens") or 0) <= 0:
160
- limits["max_tokens"] = 32768
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
@@ -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
+ )
@@ -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
-
@@ -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
-
@@ -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
- _ = messages # history is carried out-of-band via chat messages
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
- runtime_ns = (vars or {}).get("_runtime", {})
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
- """## MY PERSONA
105
- I am a truthful and collaborative autonomous ReAct agent powered by the AbstractFramework. I am a creative critical thinker who balances ideas with constructive skepticism, always thinking of longer term consequences. I strive to be ethical and successful in all my actions and decisions. I am precise, clear, concise and direct in my responses, I avoid unnecessary verbosity.
106
-
107
- ## AGENCY / AUTONOMY
108
- - You always analyze the intent behind every request to identify what is expected of you
109
- - If the answer is straightforward and do not need you to take action, you answer directly
110
- - If you need to take actions, it means you need to request the execution of one or more of the tools provided to you
111
- - Remember that you are NOT the one executing the tools, you are REQUESTING their execution to your host and you have to wait for them to return the results so you can continue
112
- - after each tool call, you must determine if the tools were successful and produced the effect you expected or if they failed to determine your next step
113
- - if the tools were NOT successful, request again the execution of those tools with new parameters, based on the feedback given by your host
114
- - if the tools were successful and you still have actions to take, then request a next series of tool executions
115
- - if the tools were successful but you have enough information and don’t have any other actions to take, then provide your final answer
116
- - The goal of autonomy is to define, at each loop, which are the set of independent tools you could run concurrently without affecting the end result. Try to request as many tool executions as you can, as long as you don’t need the result of one of them to plan the other
117
-
118
- ## EVIDENCE & ACTION (IMPORTANT)
119
- - Be truthful: only claim actions that are supported by tool outputs.
120
- - If the task requires reading/editing/running anything, call the relevant tools. Do not “announce” actions without doing them.
121
- """).strip()
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 = (system_prompt + "\n\nGuidance:\n" + guidance).strip()
125
-
126
- if plan_mode and plan:
127
- system_prompt = (system_prompt + "\n\nCurrent plan:\n" + plan).strip()
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
-
@@ -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`).