abstractagent 0.3.0__tar.gz → 0.3.1__tar.gz
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-0.3.1/PKG-INFO +112 -0
- abstractagent-0.3.1/README.md +99 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/pyproject.toml +8 -1
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent/adapters/codeact_runtime.py +86 -5
- abstractagent-0.3.1/src/abstractagent/adapters/generation_params.py +82 -0
- abstractagent-0.3.1/src/abstractagent/adapters/media.py +45 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent/adapters/memact_runtime.py +248 -10
- abstractagent-0.3.1/src/abstractagent/adapters/react_runtime.py +1612 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent/agents/base.py +31 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent/agents/codeact.py +36 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent/agents/memact.py +37 -1
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent/agents/react.py +44 -6
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent/logic/builtins.py +58 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent/logic/codeact.py +4 -1
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent/logic/memact.py +2 -1
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent/logic/react.py +37 -56
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent/tools/__init__.py +6 -0
- abstractagent-0.3.1/src/abstractagent.egg-info/PKG-INFO +112 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent.egg-info/SOURCES.txt +16 -5
- {abstractagent-0.3.0 → abstractagent-0.3.1}/tests/test_active_context_policy_adoption.py +4 -4
- {abstractagent-0.3.0 → abstractagent-0.3.1}/tests/test_agent_plan_review_modes.py +11 -48
- abstractagent-0.3.1/tests/test_generation_params_media_policies.py +28 -0
- abstractagent-0.3.1/tests/test_llm_call_payload_requires_prompt_or_messages.py +84 -0
- abstractagent-0.3.1/tests/test_open_attachment_tool_available.py +29 -0
- abstractagent-0.3.1/tests/test_react_active_attachments_disable_tools_first_iteration.py +144 -0
- abstractagent-0.3.1/tests/test_react_adapter_forwards_context_attachments_media.py +46 -0
- abstractagent-0.3.1/tests/test_react_delegate_agent_tool.py +85 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/tests/test_react_logic.py +9 -0
- abstractagent-0.3.1/tests/test_react_loop_context_transcript_levels.py +281 -0
- abstractagent-0.3.1/tests/test_react_loop_followthrough_retry.py +99 -0
- abstractagent-0.3.1/tests/test_react_loop_side_effect_dedupe_comms.py +77 -0
- abstractagent-0.3.1/tests/test_react_loop_truncated_output_retry.py +209 -0
- abstractagent-0.3.1/tests/test_react_max_iterations_concludes.py +186 -0
- abstractagent-0.3.1/tests/test_react_planish_heuristic.py +29 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/tests/test_react_workflow_system_prompt_and_tools_override.py +29 -0
- abstractagent-0.3.1/tests/test_react_workflow_toolset_refresh.py +73 -0
- abstractagent-0.3.1/tests/test_tool_observe_emits_result_for_tui.py +103 -0
- abstractagent-0.3.0/PKG-INFO +0 -133
- abstractagent-0.3.0/README.md +0 -120
- abstractagent-0.3.0/src/abstractagent/adapters/react_runtime.py +0 -1437
- abstractagent-0.3.0/src/abstractagent.egg-info/PKG-INFO +0 -133
- abstractagent-0.3.0/tests/test_prompt_deobservation.py +0 -95
- abstractagent-0.3.0/tests/test_react_runtime_parse_sanitizes_observation_transcripts.py +0 -66
- abstractagent-0.3.0/tests/test_react_runtime_tool_loop_guard.py +0 -148
- abstractagent-0.3.0/tests/test_react_runtime_verifier_policy.py +0 -97
- abstractagent-0.3.0/tests/test_toolcall_queue_and_review_prompt.py +0 -289
- {abstractagent-0.3.0 → abstractagent-0.3.1}/LICENSE +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/setup.cfg +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent/__init__.py +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent/adapters/__init__.py +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent/agents/__init__.py +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent/logic/__init__.py +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent/logic/types.py +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent/repl.py +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent/sandbox/__init__.py +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent/sandbox/interface.py +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent/sandbox/local.py +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent/scripts/__init__.py +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent/scripts/lmstudio_tool_eval.py +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent/tools/code_execution.py +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent/tools/self_improve.py +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent.egg-info/dependency_links.txt +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent.egg-info/entry_points.txt +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent.egg-info/requires.txt +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/src/abstractagent.egg-info/top_level.txt +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/tests/test_agents_integration_llm.py +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/tests/test_base_agent_trace_accessors.py +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/tests/test_builtin_tool_specs_optional_defaults.py +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/tests/test_codeact_logic.py +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/tests/test_codeact_runtime_verifier_policy.py +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/tests/test_memact_session_memory.py +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/tests/test_memact_workflow.py +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/tests/test_memory_recall_tool.py +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/tests/test_run_start_seeds_runtime_provider_model.py +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/tests/test_session_memory.py +0 -0
- {abstractagent-0.3.0 → abstractagent-0.3.1}/tests/test_tools_editing.py +0 -0
|
@@ -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`).
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# AbstractAgent
|
|
2
|
+
|
|
3
|
+
Agent patterns (ReAct / CodeAct / MemAct) built on **AbstractRuntime** (durable execution) and **AbstractCore** (tools + LLM integration).
|
|
4
|
+
|
|
5
|
+
Start here: [`docs/getting-started.md`](docs/getting-started.md) (then [`docs/README.md`](docs/README.md) for the full index)
|
|
6
|
+
|
|
7
|
+
## Documentation
|
|
8
|
+
|
|
9
|
+
- Getting started: [`docs/getting-started.md`](docs/getting-started.md)
|
|
10
|
+
- API reference: [`docs/api.md`](docs/api.md)
|
|
11
|
+
- FAQ / troubleshooting: [`docs/faq.md`](docs/faq.md)
|
|
12
|
+
- Architecture (diagrams): [`docs/architecture.md`](docs/architecture.md)
|
|
13
|
+
- Changelog: [`CHANGELOG.md`](CHANGELOG.md)
|
|
14
|
+
- Contributing: [`CONTRIBUTING.md`](CONTRIBUTING.md)
|
|
15
|
+
- Security: [`SECURITY.md`](SECURITY.md)
|
|
16
|
+
- Acknowledgements: [`ACKNOWLEDMENTS.md`](ACKNOWLEDMENTS.md)
|
|
17
|
+
|
|
18
|
+
## What you get
|
|
19
|
+
|
|
20
|
+
- **ReAct**: tool-first Reason → Act → Observe loop
|
|
21
|
+
- **CodeAct**: executes Python (tool call or fenced ` ```python``` ` blocks)
|
|
22
|
+
- **MemAct**: memory-enhanced agent using runtime-owned Active Memory
|
|
23
|
+
- **Durable runs**: pause/resume via `run_id` + runtime stores
|
|
24
|
+
- **Tool control**: explicit tool bundles + per-run allowlists
|
|
25
|
+
- **Observability**: durable ledger of LLM calls, tool calls, and waits
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
From source (development):
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install -e .
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
With dev dependencies:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install -e ".[dev]"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
From PyPI:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install abstractagent
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Note: the repository may be ahead of the latest published PyPI release. To verify what you installed:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
python -c "import importlib.metadata as md; print(md.version('abstractagent'))"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick start (ReAct)
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from abstractagent import create_react_agent
|
|
57
|
+
|
|
58
|
+
agent = create_react_agent(provider="ollama", model="qwen3:1.7b-q4_K_M")
|
|
59
|
+
agent.start("List the files in the current directory")
|
|
60
|
+
state = agent.run_to_completion()
|
|
61
|
+
print(state.output["answer"])
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Persistence (resume across restarts)
|
|
65
|
+
|
|
66
|
+
By default, the factory helpers use an in-memory runtime store. For resume across process restarts,
|
|
67
|
+
pass a persistent `RunStore`/`LedgerStore` (example below uses JSON files).
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from abstractagent import create_react_agent
|
|
71
|
+
from abstractruntime.storage.json_files import JsonFileRunStore, JsonlLedgerStore
|
|
72
|
+
|
|
73
|
+
run_store = JsonFileRunStore(".runs")
|
|
74
|
+
ledger_store = JsonlLedgerStore(".runs")
|
|
75
|
+
|
|
76
|
+
agent = create_react_agent(run_store=run_store, ledger_store=ledger_store)
|
|
77
|
+
agent.start("Long running task")
|
|
78
|
+
agent.save_state("agent_state.json")
|
|
79
|
+
|
|
80
|
+
# ... later / after restart ...
|
|
81
|
+
|
|
82
|
+
agent2 = create_react_agent(run_store=run_store, ledger_store=ledger_store)
|
|
83
|
+
agent2.load_state("agent_state.json")
|
|
84
|
+
state = agent2.run_to_completion()
|
|
85
|
+
print(state.output["answer"])
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
More details: [`docs/persistence.md`](docs/persistence.md)
|
|
89
|
+
|
|
90
|
+
## CLI
|
|
91
|
+
|
|
92
|
+
This repository still installs a `react-agent` entrypoint, but it is **deprecated** and only prints a migration hint
|
|
93
|
+
(see `src/abstractagent/repl.py` and `pyproject.toml`).
|
|
94
|
+
|
|
95
|
+
Interactive UX lives in **AbstractCode**.
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT (see `LICENSE`).
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "abstractagent"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.1"
|
|
8
8
|
description = "Agent implementations using AbstractRuntime and AbstractCore"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -23,3 +23,10 @@ where = ["src"]
|
|
|
23
23
|
|
|
24
24
|
[project.scripts]
|
|
25
25
|
react-agent = "abstractagent.repl:main"
|
|
26
|
+
|
|
27
|
+
[tool.pytest.ini_options]
|
|
28
|
+
markers = [
|
|
29
|
+
"basic: Level A (contract/unit) tests",
|
|
30
|
+
"integration: Level B (integration/local) tests",
|
|
31
|
+
"e2e: Level C (end-to-end/real infra) tests (opt-in)",
|
|
32
|
+
]
|
|
@@ -11,6 +11,8 @@ from abstractruntime import Effect, EffectType, RunState, StepPlan, WorkflowSpec
|
|
|
11
11
|
from abstractruntime.core.vars import ensure_limits, ensure_namespaces
|
|
12
12
|
from abstractruntime.memory.active_context import ActiveContextPolicy
|
|
13
13
|
|
|
14
|
+
from .generation_params import runtime_llm_params
|
|
15
|
+
from .media import extract_media_from_context
|
|
14
16
|
from ..logic.codeact import CodeActLogic
|
|
15
17
|
|
|
16
18
|
|
|
@@ -215,6 +217,7 @@ def create_codeact_workflow(
|
|
|
215
217
|
if keep < 200:
|
|
216
218
|
keep = max_chars
|
|
217
219
|
suffix = ""
|
|
220
|
+
#[WARNING:TRUNCATION] bounded message content for LLM payload
|
|
218
221
|
return text[:keep].rstrip() + suffix
|
|
219
222
|
|
|
220
223
|
out: List[Dict[str, str]] = []
|
|
@@ -335,7 +338,10 @@ def create_codeact_workflow(
|
|
|
335
338
|
|
|
336
339
|
emit("plan_request", {"tools": allow})
|
|
337
340
|
|
|
338
|
-
payload: Dict[str, Any] = {"prompt": prompt, "params": {"temperature": 0.2}}
|
|
341
|
+
payload: Dict[str, Any] = {"prompt": prompt, "params": runtime_llm_params(runtime_ns, extra={"temperature": 0.2})}
|
|
342
|
+
media = extract_media_from_context(context)
|
|
343
|
+
if media:
|
|
344
|
+
payload["media"] = media
|
|
339
345
|
sys = _system_prompt(runtime_ns)
|
|
340
346
|
if isinstance(sys, str) and sys.strip():
|
|
341
347
|
payload["system_prompt"] = sys
|
|
@@ -431,11 +437,16 @@ def create_codeact_workflow(
|
|
|
431
437
|
"messages": _sanitize_llm_messages(messages_view, limits=limits),
|
|
432
438
|
"tools": list(tool_specs),
|
|
433
439
|
}
|
|
440
|
+
media = extract_media_from_context(context)
|
|
441
|
+
if media:
|
|
442
|
+
payload["media"] = media
|
|
434
443
|
sys = _system_prompt(runtime_ns) or req.system_prompt
|
|
435
444
|
if isinstance(sys, str) and sys.strip():
|
|
436
445
|
payload["system_prompt"] = sys
|
|
446
|
+
params: Dict[str, Any] = {}
|
|
437
447
|
if req.max_tokens is not None:
|
|
438
|
-
|
|
448
|
+
params["max_tokens"] = req.max_tokens
|
|
449
|
+
payload["params"] = runtime_llm_params(runtime_ns, extra=params)
|
|
439
450
|
|
|
440
451
|
return StepPlan(
|
|
441
452
|
node_id="reason",
|
|
@@ -560,6 +571,7 @@ def create_codeact_workflow(
|
|
|
560
571
|
"remember",
|
|
561
572
|
"remember_note",
|
|
562
573
|
"compact_memory",
|
|
574
|
+
"delegate_agent",
|
|
563
575
|
}
|
|
564
576
|
|
|
565
577
|
tool_queue: List[Dict[str, Any]] = []
|
|
@@ -635,6 +647,68 @@ def create_codeact_workflow(
|
|
|
635
647
|
next_node="handle_user_response",
|
|
636
648
|
)
|
|
637
649
|
|
|
650
|
+
if name == "delegate_agent":
|
|
651
|
+
delegated_task = str(args.get("task") or "").strip()
|
|
652
|
+
delegated_context = str(args.get("context") or "").strip()
|
|
653
|
+
|
|
654
|
+
tools_raw = args.get("tools")
|
|
655
|
+
if tools_raw is None:
|
|
656
|
+
# Inherit the current allowlist, but avoid recursive delegation and avoid waiting on ask_user
|
|
657
|
+
# unless explicitly enabled.
|
|
658
|
+
child_allow = [t for t in allow if t not in {"delegate_agent", "ask_user"}]
|
|
659
|
+
else:
|
|
660
|
+
child_allow = _normalize_allowlist(tools_raw)
|
|
661
|
+
|
|
662
|
+
if not delegated_task:
|
|
663
|
+
temp["tool_results"] = {
|
|
664
|
+
"results": [
|
|
665
|
+
{
|
|
666
|
+
"call_id": str(tc.get("call_id") or ""),
|
|
667
|
+
"name": "delegate_agent",
|
|
668
|
+
"success": False,
|
|
669
|
+
"output": None,
|
|
670
|
+
"error": "delegate_agent requires a non-empty task",
|
|
671
|
+
}
|
|
672
|
+
]
|
|
673
|
+
}
|
|
674
|
+
return StepPlan(node_id="act", next_node="observe")
|
|
675
|
+
|
|
676
|
+
combined_task = delegated_task
|
|
677
|
+
if delegated_context:
|
|
678
|
+
combined_task = f"{delegated_task}\n\nContext:\n{delegated_context}"
|
|
679
|
+
|
|
680
|
+
sub_vars: Dict[str, Any] = {
|
|
681
|
+
"context": {"task": combined_task, "messages": []},
|
|
682
|
+
"_runtime": {
|
|
683
|
+
"allowed_tools": list(child_allow),
|
|
684
|
+
"system_prompt_extra": (
|
|
685
|
+
"You are a delegated sub-agent.\n"
|
|
686
|
+
"- Focus ONLY on the delegated task.\n"
|
|
687
|
+
"- Use ONLY the allowed tools when needed.\n"
|
|
688
|
+
"- Do not ask the user questions; if blocked, state assumptions and proceed.\n"
|
|
689
|
+
"- Return a concise result suitable for the parent agent to act on.\n"
|
|
690
|
+
),
|
|
691
|
+
},
|
|
692
|
+
"_limits": {"max_iterations": 10},
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
payload = {
|
|
696
|
+
"workflow_id": str(getattr(run, "workflow_id", "") or "codeact_agent"),
|
|
697
|
+
"vars": sub_vars,
|
|
698
|
+
"async": False,
|
|
699
|
+
"include_traces": False,
|
|
700
|
+
# Tool-mode wrapper so the parent receives a normal tool observation (no run failure on child failure).
|
|
701
|
+
"wrap_as_tool_result": True,
|
|
702
|
+
"tool_name": "delegate_agent",
|
|
703
|
+
"call_id": str(tc.get("call_id") or ""),
|
|
704
|
+
}
|
|
705
|
+
emit("delegate_agent", {"tools": list(child_allow), "call_id": payload.get("call_id")})
|
|
706
|
+
return StepPlan(
|
|
707
|
+
node_id="act",
|
|
708
|
+
effect=Effect(type=EffectType.START_SUBWORKFLOW, payload=payload, result_key="_temp.tool_results"),
|
|
709
|
+
next_node="observe",
|
|
710
|
+
)
|
|
711
|
+
|
|
638
712
|
if name == "recall_memory":
|
|
639
713
|
payload = dict(args)
|
|
640
714
|
payload.setdefault("tool_name", "recall_memory")
|
|
@@ -730,13 +804,14 @@ def create_codeact_workflow(
|
|
|
730
804
|
)
|
|
731
805
|
|
|
732
806
|
def execute_code_node(run: RunState, ctx) -> StepPlan:
|
|
733
|
-
_, _,
|
|
807
|
+
_, _, runtime_ns, temp, _ = ensure_codeact_vars(run)
|
|
734
808
|
code = temp.get("pending_code")
|
|
735
809
|
if not isinstance(code, str) or not code.strip():
|
|
736
810
|
return StepPlan(node_id="execute_code", next_node="reason")
|
|
737
811
|
|
|
738
812
|
temp.pop("pending_code", None)
|
|
739
813
|
emit("act", {"tool": "execute_python", "args": {"code": "(inline)", "timeout_s": 10.0}})
|
|
814
|
+
allow = _effective_allowlist(runtime_ns)
|
|
740
815
|
|
|
741
816
|
return StepPlan(
|
|
742
817
|
node_id="execute_code",
|
|
@@ -749,7 +824,8 @@ def create_codeact_workflow(
|
|
|
749
824
|
"arguments": {"code": code, "timeout_s": 10.0},
|
|
750
825
|
"call_id": "code",
|
|
751
826
|
}
|
|
752
|
-
]
|
|
827
|
+
],
|
|
828
|
+
"allowed_tools": list(allow),
|
|
753
829
|
},
|
|
754
830
|
result_key="_temp.tool_results",
|
|
755
831
|
),
|
|
@@ -794,6 +870,7 @@ def create_codeact_workflow(
|
|
|
794
870
|
# Keep a bounded preview for huge tool outputs to avoid bloating traces/ledgers.
|
|
795
871
|
preview = rendered
|
|
796
872
|
if len(preview) > 1000:
|
|
873
|
+
#[WARNING:TRUNCATION] bounded preview for observability payloads
|
|
797
874
|
preview = preview[:1000] + f"\n… (truncated, {len(rendered):,} chars total)"
|
|
798
875
|
emit("observe", {"tool": name, "success": success, "result": preview})
|
|
799
876
|
context["messages"].append(
|
|
@@ -870,6 +947,7 @@ def create_codeact_workflow(
|
|
|
870
947
|
if keep < 200:
|
|
871
948
|
keep = max_chars
|
|
872
949
|
suffix = ""
|
|
950
|
+
#[WARNING:TRUNCATION] bounded transcript blocks for prompt reconstruction
|
|
873
951
|
return s[:keep].rstrip() + suffix
|
|
874
952
|
|
|
875
953
|
def _format_allowed_tools() -> str:
|
|
@@ -1002,8 +1080,11 @@ def create_codeact_workflow(
|
|
|
1002
1080
|
"prompt": prompt,
|
|
1003
1081
|
"response_schema": schema,
|
|
1004
1082
|
"response_schema_name": "CodeActVerifier",
|
|
1005
|
-
"params": {"temperature": 0.2},
|
|
1083
|
+
"params": runtime_llm_params(runtime_ns, extra={"temperature": 0.2}),
|
|
1006
1084
|
}
|
|
1085
|
+
media = extract_media_from_context(context)
|
|
1086
|
+
if media:
|
|
1087
|
+
payload["media"] = media
|
|
1007
1088
|
sys = _system_prompt(runtime_ns)
|
|
1008
1089
|
if sys is not None:
|
|
1009
1090
|
payload["system_prompt"] = sys
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Helpers for consistent generation params in AbstractAgent adapters.
|
|
2
|
+
|
|
3
|
+
These adapters build `EffectType.LLM_CALL` payloads for AbstractRuntime. We want
|
|
4
|
+
to expose a uniform `(temperature, seed)` interface across agents while keeping
|
|
5
|
+
backward compatibility with older runs that may not have these keys in
|
|
6
|
+
`vars["_runtime"]`.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any, Dict, Optional
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def normalize_seed(seed: Any) -> Optional[int]:
|
|
15
|
+
"""Return a provider-ready seed or None when unset/random.
|
|
16
|
+
|
|
17
|
+
Policy:
|
|
18
|
+
- None or any negative value -> None (meaning: do not send seed).
|
|
19
|
+
- bool values are ignored (JSON booleans are ints in Python).
|
|
20
|
+
- numeric-ish values -> int(seed) if >= 0.
|
|
21
|
+
"""
|
|
22
|
+
try:
|
|
23
|
+
if seed is None or isinstance(seed, bool):
|
|
24
|
+
return None
|
|
25
|
+
seed_i = int(seed)
|
|
26
|
+
return seed_i if seed_i >= 0 else None
|
|
27
|
+
except Exception:
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def runtime_llm_params(
|
|
32
|
+
runtime_ns: Dict[str, Any],
|
|
33
|
+
*,
|
|
34
|
+
extra: Optional[Dict[str, Any]] = None,
|
|
35
|
+
default_temperature: float = 0.7,
|
|
36
|
+
) -> Dict[str, Any]:
|
|
37
|
+
"""Merge `runtime_ns` sampling controls into an LLM_CALL params dict.
|
|
38
|
+
|
|
39
|
+
Precedence:
|
|
40
|
+
1) `runtime_ns.temperature` / `runtime_ns.seed` when present
|
|
41
|
+
2) `extra.temperature` / `extra.seed` (step-specific defaults)
|
|
42
|
+
3) `default_temperature` (only for temperature)
|
|
43
|
+
"""
|
|
44
|
+
out: Dict[str, Any] = dict(extra or {})
|
|
45
|
+
|
|
46
|
+
# Temperature: always provide a float (provider-agnostic).
|
|
47
|
+
temp_val = runtime_ns.get("temperature") if isinstance(runtime_ns, dict) else None
|
|
48
|
+
if temp_val is None:
|
|
49
|
+
temp_val = out.get("temperature")
|
|
50
|
+
if temp_val is None:
|
|
51
|
+
temp_val = default_temperature
|
|
52
|
+
try:
|
|
53
|
+
out["temperature"] = float(temp_val)
|
|
54
|
+
except Exception:
|
|
55
|
+
out["temperature"] = float(default_temperature)
|
|
56
|
+
|
|
57
|
+
# Seed: only include when explicitly set (>= 0).
|
|
58
|
+
seed_val = runtime_ns.get("seed") if isinstance(runtime_ns, dict) else None
|
|
59
|
+
if seed_val is None:
|
|
60
|
+
seed_val = out.get("seed")
|
|
61
|
+
seed_norm = normalize_seed(seed_val)
|
|
62
|
+
if seed_norm is not None:
|
|
63
|
+
out["seed"] = seed_norm
|
|
64
|
+
else:
|
|
65
|
+
out.pop("seed", None)
|
|
66
|
+
|
|
67
|
+
# Pass-through media policies (runtime-owned defaults).
|
|
68
|
+
#
|
|
69
|
+
# This keeps thin clients simple: they can set `_runtime.audio_policy` (and
|
|
70
|
+
# optional language hints) once at run start, and all LLM_CALL steps inherit it.
|
|
71
|
+
if isinstance(runtime_ns, dict):
|
|
72
|
+
audio_policy = runtime_ns.get("audio_policy")
|
|
73
|
+
if "audio_policy" not in out and isinstance(audio_policy, str) and audio_policy.strip():
|
|
74
|
+
out["audio_policy"] = audio_policy.strip()
|
|
75
|
+
|
|
76
|
+
stt_language = runtime_ns.get("stt_language")
|
|
77
|
+
if stt_language is None:
|
|
78
|
+
stt_language = runtime_ns.get("audio_language")
|
|
79
|
+
if "stt_language" not in out and isinstance(stt_language, str) and stt_language.strip():
|
|
80
|
+
out["stt_language"] = stt_language.strip()
|
|
81
|
+
|
|
82
|
+
return out
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Helpers for attachment/media plumbing in runtime-backed agents."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def extract_media_from_context(context: Dict[str, Any]) -> Optional[List[Any]]:
|
|
9
|
+
"""Return a normalized `media` list from a runtime `context` dict.
|
|
10
|
+
|
|
11
|
+
Supported keys (best-effort):
|
|
12
|
+
- `context["attachments"]`: preferred (artifact refs)
|
|
13
|
+
- `context["media"]`: legacy/alternate
|
|
14
|
+
"""
|
|
15
|
+
raw = context.get("attachments")
|
|
16
|
+
if raw is None:
|
|
17
|
+
raw = context.get("media")
|
|
18
|
+
|
|
19
|
+
if isinstance(raw, tuple):
|
|
20
|
+
items = list(raw)
|
|
21
|
+
else:
|
|
22
|
+
items = raw
|
|
23
|
+
|
|
24
|
+
if not isinstance(items, list) or not items:
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
out: List[Any] = []
|
|
28
|
+
for item in items:
|
|
29
|
+
if isinstance(item, str):
|
|
30
|
+
s = item.strip()
|
|
31
|
+
if s:
|
|
32
|
+
out.append(s)
|
|
33
|
+
continue
|
|
34
|
+
|
|
35
|
+
if isinstance(item, dict):
|
|
36
|
+
# Prefer artifact refs; accept both {"$artifact": "..."} and {"artifact_id": "..."}.
|
|
37
|
+
aid = item.get("$artifact")
|
|
38
|
+
if not (isinstance(aid, str) and aid.strip()):
|
|
39
|
+
aid = item.get("artifact_id")
|
|
40
|
+
if isinstance(aid, str) and aid.strip():
|
|
41
|
+
out.append(dict(item))
|
|
42
|
+
continue
|
|
43
|
+
|
|
44
|
+
return out or None
|
|
45
|
+
|