AbstractAgent 0.3.18__tar.gz → 0.4.0__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.18/src/abstractagent.egg-info → abstractagent-0.4.0}/PKG-INFO +15 -14
- {abstractagent-0.3.18 → abstractagent-0.4.0}/README.md +8 -7
- {abstractagent-0.3.18 → abstractagent-0.4.0}/pyproject.toml +7 -7
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/adapters/emit_inventory.py +6 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/adapters/react_runtime.py +60 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/agents/react.py +2 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/logic/builtins.py +63 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0/src/abstractagent.egg-info}/PKG-INFO +15 -14
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent.egg-info/SOURCES.txt +2 -0
- abstractagent-0.4.0/src/abstractagent.egg-info/requires.txt +16 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_packaging_profiles.py +6 -6
- abstractagent-0.4.0/tests/test_react_schedule_run_tool.py +99 -0
- abstractagent-0.4.0/tests/test_react_tool_format_retry.py +106 -0
- abstractagent-0.3.18/src/abstractagent.egg-info/requires.txt +0 -16
- {abstractagent-0.3.18 → abstractagent-0.4.0}/LICENSE +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/setup.cfg +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/__init__.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/adapters/__init__.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/adapters/codeact_runtime.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/adapters/generation_params.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/adapters/loop_hooks.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/adapters/media.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/adapters/memact_runtime.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/adapters/native_loop_registry.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/adapters/progress.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/adapters/read_orchestration.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/adapters/tool_allowlist.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/adapters/tool_failure_hints.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/adapters/transcripts.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/agents/__init__.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/agents/base.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/agents/codeact.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/agents/defaults.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/agents/memact.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/agents/task_reset.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/agents/unattended.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/logic/__init__.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/logic/codeact.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/logic/memact.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/logic/react.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/logic/types.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/metrics/__init__.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/metrics/prefix_reuse.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/repl.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/sandbox/__init__.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/sandbox/interface.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/sandbox/local.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/scripts/__init__.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/scripts/lmstudio_tool_eval.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/tools/__init__.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/tools/code_execution.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/tools/self_improve.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent.egg-info/dependency_links.txt +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent.egg-info/entry_points.txt +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent.egg-info/top_level.txt +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_active_context_policy_adoption.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_adversary_hardening_2026_07_13.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_agent_plan_review_modes.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_agent_speculation_inheritance.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_agent_stream_inheritance.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_agents_integration_llm.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_base_agent_trace_accessors.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_batching_prompt_rule.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_budget_triage_tail.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_builtin_tool_specs_optional_defaults.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_c2447_visit_lane_honesty.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_claim_0029_residue_2026_07_14.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_codeact_logic.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_codeact_runtime_verifier_policy.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_delegate_session_route_composition.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_emit_inventory_drift.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_execute_python_sandbox.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_gated_remainder_wave_2026_07_14.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_generation_params_media_policies.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_history_monster_clamp.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_improvement_wave_2026_07_13.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_llm_call_payload_requires_prompt_or_messages.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_lmstudio_tool_eval_metrics.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_loop_hooks.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_loop_hooks_0026_follow_ups.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_memact_codeact_guidance_durable_transcript.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_memact_session_memory.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_memact_workflow.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_memory_recall_tool.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_native_loop_registry.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_open_attachment_tool_available.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_operator_conclude.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_optimal_wave_batch2_2026_07_13.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_parse_prompt_cache_capture.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_prefix_reuse_metric.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_progress_detection.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_act_only_observation.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_active_attachments_disable_tools_first_iteration.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_adapter_forwards_context_attachments_media.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_delegate_agent_tool.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_election_fence_conformance.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_entity_dress_conformance.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_guidance_durable_transcript.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_history_window.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_logic.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_loop_aborted_generation_retry.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_loop_context_transcript_levels.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_loop_followthrough_retry.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_loop_side_effect_dedupe_comms.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_loop_truncated_output_retry.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_max_iterations_concludes.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_phase_grant_boundary.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_planish_heuristic.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_prompt_prefix_stability.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_sight_lane_media_fold.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_turn_byte_stability.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_verifier_and_plan.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_visit_composition.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_visit_merge.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_workflow_system_prompt_and_tools_override.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_workflow_toolset_refresh.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_read_orchestration_guidance.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_reasoning_first_citizen_agent.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_review_failure_containment.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_run_start_seeds_runtime_provider_model.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_session_memory.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_sibling_loop_defects_batch.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_sibling_strict_transcripts_0011.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_stuck_streak_termination.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_tool_failure_hints.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_tool_loop_prefix_extension_a3.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_tool_observe_emits_result_for_tui.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_tools_editing.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_unseeded_render_limits.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_verifier_execution_preference.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_verifier_schema_strict.py +0 -0
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: abstractagent
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Agent implementations using AbstractRuntime and AbstractCore
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
7
|
License-File: LICENSE
|
|
8
|
-
Requires-Dist: abstractcore[tools]>=2.
|
|
9
|
-
Requires-Dist: abstractruntime>=0.
|
|
8
|
+
Requires-Dist: abstractcore[tools]>=2.30.0
|
|
9
|
+
Requires-Dist: abstractruntime>=0.12.0
|
|
10
10
|
Provides-Extra: apple
|
|
11
|
-
Requires-Dist: abstractcore[all-apple]>=2.
|
|
12
|
-
Requires-Dist: abstractruntime[apple]>=0.
|
|
11
|
+
Requires-Dist: abstractcore[all-apple]>=2.30.0; extra == "apple"
|
|
12
|
+
Requires-Dist: abstractruntime[apple]>=0.12.0; extra == "apple"
|
|
13
13
|
Provides-Extra: gpu
|
|
14
|
-
Requires-Dist: abstractcore[all-gpu]>=2.
|
|
15
|
-
Requires-Dist: abstractruntime[gpu]>=0.
|
|
14
|
+
Requires-Dist: abstractcore[all-gpu]>=2.30.0; extra == "gpu"
|
|
15
|
+
Requires-Dist: abstractruntime[gpu]>=0.12.0; extra == "gpu"
|
|
16
16
|
Provides-Extra: dev
|
|
17
17
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
18
18
|
Provides-Extra: test
|
|
@@ -68,6 +68,9 @@ flowchart LR
|
|
|
68
68
|
- **Generation controls**: temperature, seed, media policy, prompt-cache
|
|
69
69
|
identity, streaming, Core `thinking` and `speculation` (MTP) are normalized
|
|
70
70
|
before LLM calls and inherited by delegated children
|
|
71
|
+
- **Scheduled runs** (ReAct): the `schedule_run` tool schedules work or a reminder
|
|
72
|
+
for later in the same conversation, with at most the agent's tools (see
|
|
73
|
+
[`docs/tools.md`](docs/tools.md))
|
|
71
74
|
- **Observability**: durable ledger of LLM calls, tool calls, and waits
|
|
72
75
|
|
|
73
76
|
Where this lives in code (source of truth):
|
|
@@ -105,13 +108,11 @@ Native Python hardware profile cascades are available for deployment manifests:
|
|
|
105
108
|
AbstractCore and AbstractRuntime profiles; AbstractAgent itself remains
|
|
106
109
|
provider/runtime agnostic.
|
|
107
110
|
|
|
108
|
-
AbstractAgent 0.
|
|
109
|
-
`AbstractRuntime>=0.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
speculation inheritance). AbstractRuntime 0.7.0 itself requires AbstractCore
|
|
114
|
-
2.18.0 or newer.
|
|
111
|
+
AbstractAgent 0.4.0 requires `abstractcore[tools]>=2.30.0` and
|
|
112
|
+
`AbstractRuntime>=0.12.0` (the runtime release with the `SCHEDULE_RUN` effect
|
|
113
|
+
behind the ReAct `schedule_run` tool, on top of the 50k-token history window
|
|
114
|
+
that ReAct applies when a host sets `_runtime.history_window_tokens`, live token
|
|
115
|
+
streaming, turn grounding, in-flight cancellation and speculation inheritance).
|
|
115
116
|
|
|
116
117
|
Note: the repository may be ahead of the latest published PyPI release. To verify what you installed:
|
|
117
118
|
|
|
@@ -47,6 +47,9 @@ flowchart LR
|
|
|
47
47
|
- **Generation controls**: temperature, seed, media policy, prompt-cache
|
|
48
48
|
identity, streaming, Core `thinking` and `speculation` (MTP) are normalized
|
|
49
49
|
before LLM calls and inherited by delegated children
|
|
50
|
+
- **Scheduled runs** (ReAct): the `schedule_run` tool schedules work or a reminder
|
|
51
|
+
for later in the same conversation, with at most the agent's tools (see
|
|
52
|
+
[`docs/tools.md`](docs/tools.md))
|
|
50
53
|
- **Observability**: durable ledger of LLM calls, tool calls, and waits
|
|
51
54
|
|
|
52
55
|
Where this lives in code (source of truth):
|
|
@@ -84,13 +87,11 @@ Native Python hardware profile cascades are available for deployment manifests:
|
|
|
84
87
|
AbstractCore and AbstractRuntime profiles; AbstractAgent itself remains
|
|
85
88
|
provider/runtime agnostic.
|
|
86
89
|
|
|
87
|
-
AbstractAgent 0.
|
|
88
|
-
`AbstractRuntime>=0.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
speculation inheritance). AbstractRuntime 0.7.0 itself requires AbstractCore
|
|
93
|
-
2.18.0 or newer.
|
|
90
|
+
AbstractAgent 0.4.0 requires `abstractcore[tools]>=2.30.0` and
|
|
91
|
+
`AbstractRuntime>=0.12.0` (the runtime release with the `SCHEDULE_RUN` effect
|
|
92
|
+
behind the ReAct `schedule_run` tool, on top of the 50k-token history window
|
|
93
|
+
that ReAct applies when a host sets `_runtime.history_window_tokens`, live token
|
|
94
|
+
streaming, turn grounding, in-flight cancellation and speculation inheritance).
|
|
94
95
|
|
|
95
96
|
Note: the repository may be ahead of the latest published PyPI release. To verify what you installed:
|
|
96
97
|
|
|
@@ -4,23 +4,23 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "abstractagent"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "Agent implementations using AbstractRuntime and AbstractCore"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
11
|
dependencies = [
|
|
12
|
-
"abstractcore[tools]>=2.
|
|
13
|
-
"abstractruntime>=0.
|
|
12
|
+
"abstractcore[tools]>=2.30.0",
|
|
13
|
+
"abstractruntime>=0.12.0",
|
|
14
14
|
]
|
|
15
15
|
|
|
16
16
|
[project.optional-dependencies]
|
|
17
17
|
apple = [
|
|
18
|
-
"abstractcore[all-apple]>=2.
|
|
19
|
-
"abstractruntime[apple]>=0.
|
|
18
|
+
"abstractcore[all-apple]>=2.30.0",
|
|
19
|
+
"abstractruntime[apple]>=0.12.0",
|
|
20
20
|
]
|
|
21
21
|
gpu = [
|
|
22
|
-
"abstractcore[all-gpu]>=2.
|
|
23
|
-
"abstractruntime[gpu]>=0.
|
|
22
|
+
"abstractcore[all-gpu]>=2.30.0",
|
|
23
|
+
"abstractruntime[gpu]>=0.12.0",
|
|
24
24
|
]
|
|
25
25
|
dev = [
|
|
26
26
|
"pytest>=7.0",
|
|
@@ -85,6 +85,12 @@ REACT_STEPS = COMMON_STEPS | frozenset(
|
|
|
85
85
|
"parse_retry_empty",
|
|
86
86
|
"parse_retry_plan_only",
|
|
87
87
|
"parse_retry_truncated",
|
|
88
|
+
# Tool-call format repair (2026-10-03, shipped round 14): AbstractCore
|
|
89
|
+
# reported `metadata.tool_call_error` (code invalid_tool_syntax |
|
|
90
|
+
# unavailable_tool) and accepted ZERO calls; the model is asked once
|
|
91
|
+
# more with the tool roster — payload {cycle, attempt, code}. At most
|
|
92
|
+
# two per turn; the third raises (the run fails with the sentence).
|
|
93
|
+
"parse_retry_tool_format",
|
|
88
94
|
# 0017 work half (2026-07-21), nudge-then-stop (2026-08-21): fires on
|
|
89
95
|
# every detection of a consecutive-identical or A-B-A-B tool-batch
|
|
90
96
|
# streak; payload {kind: repeat|oscillation, span, key, answered,
|
|
@@ -1446,6 +1446,10 @@ def reset_react_turn(run_vars: Dict[str, Any]) -> None:
|
|
|
1446
1446
|
# retry stash is the same lifecycle class.
|
|
1447
1447
|
"pending_media",
|
|
1448
1448
|
"last_call_media",
|
|
1449
|
+
# Tool-call format repair budget is per TURN (two retries each):
|
|
1450
|
+
# without this pop, two format failures anywhere in a conversation
|
|
1451
|
+
# made every later failing turn raise at once.
|
|
1452
|
+
"tool_format_retries",
|
|
1449
1453
|
):
|
|
1450
1454
|
temp.pop(key, None)
|
|
1451
1455
|
|
|
@@ -1736,6 +1740,7 @@ def create_react_workflow(
|
|
|
1736
1740
|
"remember_note",
|
|
1737
1741
|
"compact_memory",
|
|
1738
1742
|
"delegate_agent",
|
|
1743
|
+
"schedule_run",
|
|
1739
1744
|
"update_plan",
|
|
1740
1745
|
}
|
|
1741
1746
|
|
|
@@ -2496,6 +2501,36 @@ def create_react_workflow(
|
|
|
2496
2501
|
emit("parse_tool_calls", {"count": len(tool_calls)})
|
|
2497
2502
|
return StepPlan(node_id="parse", next_node="act")
|
|
2498
2503
|
|
|
2504
|
+
# Core owns syntax recognition; the agent owns bounded recovery. Retry
|
|
2505
|
+
# only when zero calls were accepted, so a partial batch is never replayed.
|
|
2506
|
+
metadata = response.get("metadata") if isinstance(response, dict) else None
|
|
2507
|
+
tool_error = metadata.get("tool_call_error") if isinstance(metadata, dict) else None
|
|
2508
|
+
if isinstance(tool_error, dict) and tool_error.get("code") in {"invalid_tool_syntax", "unavailable_tool"}:
|
|
2509
|
+
retries = int(temp.get("tool_format_retries", 0) or 0)
|
|
2510
|
+
if retries >= 2 or cycle_i >= max_iterations:
|
|
2511
|
+
raise RuntimeError("Tool-call format repair failed: no executable tool calls after bounded retries.")
|
|
2512
|
+
specs = runtime_ns.get("tool_specs") or []
|
|
2513
|
+
names = [str(spec["name"]) for spec in specs if isinstance(spec, dict) and spec.get("name")]
|
|
2514
|
+
if not names:
|
|
2515
|
+
raise RuntimeError("The model attempted a tool call, but this run has no tools configured.")
|
|
2516
|
+
temp["tool_format_retries"] = retries + 1
|
|
2517
|
+
_push_inbox(
|
|
2518
|
+
runtime_ns,
|
|
2519
|
+
"Your previous tool call was rejected; nothing was executed. "
|
|
2520
|
+
"Use only the available tools: " + ", ".join(names) + ". "
|
|
2521
|
+
# No format of our own: the tool-calling format is the one the
|
|
2522
|
+
# request's instructions carry (native structured calls, or the
|
|
2523
|
+
# provider's prompted text format) — a hard-coded text syntax
|
|
2524
|
+
# here would teach a native tool-calling model the wrong one.
|
|
2525
|
+
"Follow the tool-calling format and argument schemas supplied in the instructions, "
|
|
2526
|
+
"with an actual available tool name and valid arguments. "
|
|
2527
|
+
"Emit the corrected call now, or explain why you cannot complete the task without it.",
|
|
2528
|
+
)
|
|
2529
|
+
emit("parse_retry_tool_format", {"cycle": cycle_i, "attempt": retries + 1, "code": tool_error["code"]})
|
|
2530
|
+
if last_call_media:
|
|
2531
|
+
temp["pending_media"] = last_call_media
|
|
2532
|
+
return StepPlan(node_id="parse", next_node="reason")
|
|
2533
|
+
|
|
2499
2534
|
# If the model hit an output limit, treat the step as incomplete and continue.
|
|
2500
2535
|
# Retry-nudge lane honesty (iteration-3 adversary P0, 2026-07-19 — the
|
|
2501
2536
|
# c2447 chrome class at the PARSE boundary): these nudges ride the
|
|
@@ -3012,6 +3047,31 @@ def create_react_workflow(
|
|
|
3012
3047
|
next_node="observe",
|
|
3013
3048
|
)
|
|
3014
3049
|
|
|
3050
|
+
if name == "schedule_run":
|
|
3051
|
+
# Framework backlog 1045: a run LATER (at a time, after a delay, or on a trigger
|
|
3052
|
+
# event) with at most this run's tools, never schedule_run itself (no recursion,
|
|
3053
|
+
# like delegate_agent). The runtime intersects again with the run tree's ceiling
|
|
3054
|
+
# and the host creates the one-shot automation (EffectType.SCHEDULE_RUN).
|
|
3055
|
+
tools_raw = args.get("tools")
|
|
3056
|
+
if tools_raw is None:
|
|
3057
|
+
wake_tools = [t for t in allow if t != "schedule_run"]
|
|
3058
|
+
else:
|
|
3059
|
+
parent_allow = set(allow)
|
|
3060
|
+
wake_tools = [t for t in _normalize_allowlist(tools_raw) if t in parent_allow and t != "schedule_run"]
|
|
3061
|
+
payload = {
|
|
3062
|
+
k: args[k]
|
|
3063
|
+
for k in ("at", "after_s", "on", "task", "note", "mode", "title", "notify")
|
|
3064
|
+
if args.get(k) is not None
|
|
3065
|
+
}
|
|
3066
|
+
payload["tools"] = wake_tools
|
|
3067
|
+
payload["tool_name"] = "schedule_run"
|
|
3068
|
+
payload["call_id"] = str(tc.get("call_id") or "")
|
|
3069
|
+
return StepPlan(
|
|
3070
|
+
node_id="act",
|
|
3071
|
+
effect=Effect(type=EffectType.SCHEDULE_RUN, payload=payload, result_key="_temp.tool_results"),
|
|
3072
|
+
next_node="observe",
|
|
3073
|
+
)
|
|
3074
|
+
|
|
3015
3075
|
# Unknown builtin: continue.
|
|
3016
3076
|
return StepPlan(node_id="act", next_node="act" if temp.get("pending_tool_calls") else "reason")
|
|
3017
3077
|
|
|
@@ -24,6 +24,7 @@ from ..logic.builtins import (
|
|
|
24
24
|
ASK_USER_TOOL,
|
|
25
25
|
COMPACT_MEMORY_TOOL,
|
|
26
26
|
DELEGATE_AGENT_TOOL,
|
|
27
|
+
SCHEDULE_RUN_TOOL,
|
|
27
28
|
INSPECT_VARS_TOOL,
|
|
28
29
|
OPEN_ATTACHMENT_TOOL,
|
|
29
30
|
RECALL_MEMORY_TOOL,
|
|
@@ -121,6 +122,7 @@ class ReactAgent(BaseAgent):
|
|
|
121
122
|
REMEMBER_NOTE_TOOL,
|
|
122
123
|
COMPACT_MEMORY_TOOL,
|
|
123
124
|
DELEGATE_AGENT_TOOL,
|
|
125
|
+
SCHEDULE_RUN_TOOL,
|
|
124
126
|
UPDATE_PLAN_TOOL,
|
|
125
127
|
*tool_defs,
|
|
126
128
|
]
|
|
@@ -364,3 +364,66 @@ DELEGATE_AGENT_TOOL = ToolDefinition(
|
|
|
364
364
|
"inspect a few files, search for specific symbols, summarize findings)."
|
|
365
365
|
),
|
|
366
366
|
)
|
|
367
|
+
|
|
368
|
+
SCHEDULE_RUN_TOOL = ToolDefinition(
|
|
369
|
+
name="schedule_run",
|
|
370
|
+
description="Schedule a run for later (at a time, after a delay, or on a trigger event) with a task or a reminder note.",
|
|
371
|
+
parameters={
|
|
372
|
+
"after_s": {
|
|
373
|
+
"type": "number",
|
|
374
|
+
"description": "Start this many seconds from now (e.g. 300 for five minutes). Give one of after_s, at or on.",
|
|
375
|
+
"default": None,
|
|
376
|
+
},
|
|
377
|
+
"at": {
|
|
378
|
+
"type": "string",
|
|
379
|
+
"description": "Start at this time: an RFC 3339 timestamp with its offset, e.g. 2026-10-26T09:00:00+01:00.",
|
|
380
|
+
"default": None,
|
|
381
|
+
},
|
|
382
|
+
"on": {
|
|
383
|
+
"type": "object",
|
|
384
|
+
"description": (
|
|
385
|
+
"Start when a trigger event arrives: {source_id, source_version?, connection_id?, config?}, "
|
|
386
|
+
"e.g. {\"source_id\": \"feed.item\", \"connection_id\": \"tc_...\"}. One run, on the first event."
|
|
387
|
+
),
|
|
388
|
+
"default": None,
|
|
389
|
+
},
|
|
390
|
+
"task": {
|
|
391
|
+
"type": "string",
|
|
392
|
+
"description": "What the scheduled run should do (self-contained). Give a task or a note.",
|
|
393
|
+
"default": None,
|
|
394
|
+
},
|
|
395
|
+
"note": {
|
|
396
|
+
"type": "string",
|
|
397
|
+
"description": "A reminder to bring back to the person at that time (no tools are used).",
|
|
398
|
+
"default": None,
|
|
399
|
+
},
|
|
400
|
+
"tools": {
|
|
401
|
+
"type": "array",
|
|
402
|
+
"items": {"type": "string"},
|
|
403
|
+
"description": "Tools the scheduled run may use (a subset of yours). When omitted it inherits yours.",
|
|
404
|
+
"default": None,
|
|
405
|
+
},
|
|
406
|
+
"mode": {
|
|
407
|
+
"type": "string",
|
|
408
|
+
"description": "same_session (default: a new turn of this conversation) or independent (a fresh conversation).",
|
|
409
|
+
"default": "same_session",
|
|
410
|
+
},
|
|
411
|
+
"title": {
|
|
412
|
+
"type": "string",
|
|
413
|
+
"description": "A short title for the scheduled run (optional).",
|
|
414
|
+
"default": None,
|
|
415
|
+
},
|
|
416
|
+
"notify": {
|
|
417
|
+
"type": "boolean",
|
|
418
|
+
"description": (
|
|
419
|
+
"Send the person a notification with the scheduled run's answer (optional). Default: true "
|
|
420
|
+
"for a note (a reminder reaches them), false for a task."
|
|
421
|
+
),
|
|
422
|
+
"default": None,
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
when_to_use=(
|
|
426
|
+
"Use when the person asks to be reminded later, or when work must happen later or when something "
|
|
427
|
+
"arrives. The run joins this conversation by default, with at most your tools."
|
|
428
|
+
),
|
|
429
|
+
)
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: abstractagent
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Agent implementations using AbstractRuntime and AbstractCore
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
7
|
License-File: LICENSE
|
|
8
|
-
Requires-Dist: abstractcore[tools]>=2.
|
|
9
|
-
Requires-Dist: abstractruntime>=0.
|
|
8
|
+
Requires-Dist: abstractcore[tools]>=2.30.0
|
|
9
|
+
Requires-Dist: abstractruntime>=0.12.0
|
|
10
10
|
Provides-Extra: apple
|
|
11
|
-
Requires-Dist: abstractcore[all-apple]>=2.
|
|
12
|
-
Requires-Dist: abstractruntime[apple]>=0.
|
|
11
|
+
Requires-Dist: abstractcore[all-apple]>=2.30.0; extra == "apple"
|
|
12
|
+
Requires-Dist: abstractruntime[apple]>=0.12.0; extra == "apple"
|
|
13
13
|
Provides-Extra: gpu
|
|
14
|
-
Requires-Dist: abstractcore[all-gpu]>=2.
|
|
15
|
-
Requires-Dist: abstractruntime[gpu]>=0.
|
|
14
|
+
Requires-Dist: abstractcore[all-gpu]>=2.30.0; extra == "gpu"
|
|
15
|
+
Requires-Dist: abstractruntime[gpu]>=0.12.0; extra == "gpu"
|
|
16
16
|
Provides-Extra: dev
|
|
17
17
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
18
18
|
Provides-Extra: test
|
|
@@ -68,6 +68,9 @@ flowchart LR
|
|
|
68
68
|
- **Generation controls**: temperature, seed, media policy, prompt-cache
|
|
69
69
|
identity, streaming, Core `thinking` and `speculation` (MTP) are normalized
|
|
70
70
|
before LLM calls and inherited by delegated children
|
|
71
|
+
- **Scheduled runs** (ReAct): the `schedule_run` tool schedules work or a reminder
|
|
72
|
+
for later in the same conversation, with at most the agent's tools (see
|
|
73
|
+
[`docs/tools.md`](docs/tools.md))
|
|
71
74
|
- **Observability**: durable ledger of LLM calls, tool calls, and waits
|
|
72
75
|
|
|
73
76
|
Where this lives in code (source of truth):
|
|
@@ -105,13 +108,11 @@ Native Python hardware profile cascades are available for deployment manifests:
|
|
|
105
108
|
AbstractCore and AbstractRuntime profiles; AbstractAgent itself remains
|
|
106
109
|
provider/runtime agnostic.
|
|
107
110
|
|
|
108
|
-
AbstractAgent 0.
|
|
109
|
-
`AbstractRuntime>=0.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
speculation inheritance). AbstractRuntime 0.7.0 itself requires AbstractCore
|
|
114
|
-
2.18.0 or newer.
|
|
111
|
+
AbstractAgent 0.4.0 requires `abstractcore[tools]>=2.30.0` and
|
|
112
|
+
`AbstractRuntime>=0.12.0` (the runtime release with the `SCHEDULE_RUN` effect
|
|
113
|
+
behind the ReAct `schedule_run` tool, on top of the 50k-token history window
|
|
114
|
+
that ReAct applies when a host sets `_runtime.history_window_tokens`, live token
|
|
115
|
+
streaming, turn grounding, in-flight cancellation and speculation inheritance).
|
|
115
116
|
|
|
116
117
|
Note: the repository may be ahead of the latest published PyPI release. To verify what you installed:
|
|
117
118
|
|
|
@@ -102,7 +102,9 @@ tests/test_react_max_iterations_concludes.py
|
|
|
102
102
|
tests/test_react_phase_grant_boundary.py
|
|
103
103
|
tests/test_react_planish_heuristic.py
|
|
104
104
|
tests/test_react_prompt_prefix_stability.py
|
|
105
|
+
tests/test_react_schedule_run_tool.py
|
|
105
106
|
tests/test_react_sight_lane_media_fold.py
|
|
107
|
+
tests/test_react_tool_format_retry.py
|
|
106
108
|
tests/test_react_turn_byte_stability.py
|
|
107
109
|
tests/test_react_verifier_and_plan.py
|
|
108
110
|
tests/test_react_visit_composition.py
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
abstractcore[tools]>=2.30.0
|
|
2
|
+
abstractruntime>=0.12.0
|
|
3
|
+
|
|
4
|
+
[apple]
|
|
5
|
+
abstractcore[all-apple]>=2.30.0
|
|
6
|
+
abstractruntime[apple]>=0.12.0
|
|
7
|
+
|
|
8
|
+
[dev]
|
|
9
|
+
pytest>=7.0
|
|
10
|
+
|
|
11
|
+
[gpu]
|
|
12
|
+
abstractcore[all-gpu]>=2.30.0
|
|
13
|
+
abstractruntime[gpu]>=0.12.0
|
|
14
|
+
|
|
15
|
+
[test]
|
|
16
|
+
pytest>=7.0
|
|
@@ -18,20 +18,20 @@ def _project() -> dict:
|
|
|
18
18
|
def test_base_dependency_floors_match_gateway_alignment() -> None:
|
|
19
19
|
deps = set(_project()["dependencies"])
|
|
20
20
|
|
|
21
|
-
assert "abstractcore[tools]>=2.
|
|
22
|
-
assert "abstractruntime>=0.
|
|
21
|
+
assert "abstractcore[tools]>=2.30.0" in deps
|
|
22
|
+
assert "abstractruntime>=0.12.0" in deps
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def test_hardware_profile_extras_are_core_runtime_cascades() -> None:
|
|
26
26
|
extras = _project()["optional-dependencies"]
|
|
27
27
|
|
|
28
28
|
assert set(extras["apple"]) == {
|
|
29
|
-
"abstractcore[all-apple]>=2.
|
|
30
|
-
"abstractruntime[apple]>=0.
|
|
29
|
+
"abstractcore[all-apple]>=2.30.0",
|
|
30
|
+
"abstractruntime[apple]>=0.12.0",
|
|
31
31
|
}
|
|
32
32
|
assert set(extras["gpu"]) == {
|
|
33
|
-
"abstractcore[all-gpu]>=2.
|
|
34
|
-
"abstractruntime[gpu]>=0.
|
|
33
|
+
"abstractcore[all-gpu]>=2.30.0",
|
|
34
|
+
"abstractruntime[gpu]>=0.12.0",
|
|
35
35
|
}
|
|
36
36
|
assert "all-apple" not in extras
|
|
37
37
|
assert "all-gpu" not in extras
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""The `schedule_run` builtin (framework backlog 1045): a ReAct call becomes ONE `SCHEDULE_RUN` effect
|
|
2
|
+
with at most the agent's tools and never `schedule_run` itself; the host creates the wake."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from abstractagent.adapters.react_runtime import create_react_workflow
|
|
11
|
+
from abstractagent.logic.builtins import SCHEDULE_RUN_TOOL
|
|
12
|
+
from abstractagent.logic.react import ReActLogic
|
|
13
|
+
from abstractcore.tools import ToolDefinition
|
|
14
|
+
from abstractruntime.core.models import Effect, EffectType, RunState, RunStatus
|
|
15
|
+
from abstractruntime.core.runtime import EffectOutcome, Runtime
|
|
16
|
+
from abstractruntime.scheduler.registry import WorkflowRegistry
|
|
17
|
+
from abstractruntime.storage.in_memory import InMemoryLedgerStore, InMemoryRunStore
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _run(arguments: Dict[str, Any], *, allowed_tools: List[str], handler: Any) -> RunState:
|
|
21
|
+
calls: Dict[str, int] = {}
|
|
22
|
+
|
|
23
|
+
def llm_handler(run: RunState, effect: Effect, default_next_node: Optional[str]) -> EffectOutcome:
|
|
24
|
+
calls[run.run_id] = calls.get(run.run_id, 0) + 1
|
|
25
|
+
if calls[run.run_id] == 1:
|
|
26
|
+
return EffectOutcome.completed({"content": "Scheduling.", "finish_reason": "tool_calls",
|
|
27
|
+
"tool_calls": [{"name": "schedule_run", "arguments": arguments, "call_id": "call_1"}]})
|
|
28
|
+
return EffectOutcome.completed({"content": "Done.", "tool_calls": [], "finish_reason": "stop"})
|
|
29
|
+
|
|
30
|
+
runtime = Runtime(run_store=InMemoryRunStore(), ledger_store=InMemoryLedgerStore(),
|
|
31
|
+
effect_handlers={EffectType.LLM_CALL: llm_handler}, workflow_registry=WorkflowRegistry())
|
|
32
|
+
runtime.set_schedule_run_handler(handler)
|
|
33
|
+
tool_defs = [SCHEDULE_RUN_TOOL, ToolDefinition(name="read_file", description="Read", parameters={}),
|
|
34
|
+
ToolDefinition(name="execute_command", description="Run", parameters={})]
|
|
35
|
+
workflow = create_react_workflow(logic=ReActLogic(tools=tool_defs), workflow_id="react_agent")
|
|
36
|
+
runtime.workflow_registry.register(workflow)
|
|
37
|
+
run_id = runtime.start(workflow=workflow, session_id="chat-S",
|
|
38
|
+
vars={"context": {"task": "Remind me", "messages": []}, "_runtime": {"inbox": [], "allowed_tools": allowed_tools}})
|
|
39
|
+
for _ in range(200):
|
|
40
|
+
state = runtime.tick(workflow=workflow, run_id=run_id, max_steps=1)
|
|
41
|
+
if state.status in (RunStatus.COMPLETED, RunStatus.FAILED):
|
|
42
|
+
break
|
|
43
|
+
return runtime.get_state(run_id)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _tool_messages(final: RunState) -> List[str]:
|
|
47
|
+
msgs = (final.output or {}).get("messages") or []
|
|
48
|
+
return [str(m.get("content") or "") for m in msgs if isinstance(m, dict) and m.get("role") == "tool"]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pytest.mark.basic
|
|
52
|
+
def test_schedule_run_passes_at_most_the_agents_tools_never_itself() -> None:
|
|
53
|
+
plans: List[Dict[str, Any]] = []
|
|
54
|
+
|
|
55
|
+
def host(run, plan):
|
|
56
|
+
plans.append(plan)
|
|
57
|
+
return {"automation_id": "auto-1"}
|
|
58
|
+
|
|
59
|
+
final = _run({"after_s": 300, "task": "check the build", "tools": ["read_file", "execute_command", "schedule_run"]},
|
|
60
|
+
allowed_tools=["schedule_run", "read_file"], handler=host)
|
|
61
|
+
assert final.status == RunStatus.COMPLETED
|
|
62
|
+
(plan,) = plans
|
|
63
|
+
assert plan["allowed_tools"] == ["read_file"] # execute_command is not the agent's; schedule_run never passes on
|
|
64
|
+
assert plan["session_id"] == "chat-S" and plan["mode"] == "same_session" and plan["kind"] == "task"
|
|
65
|
+
assert plan["input_data"]["_runtime"]["allowed_tools"] == ["read_file"]
|
|
66
|
+
assert any("auto-1" in m for m in _tool_messages(final))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@pytest.mark.basic
|
|
70
|
+
def test_schedule_run_inherits_the_agents_tools_when_omitted_and_a_note_gets_none() -> None:
|
|
71
|
+
plans: List[Dict[str, Any]] = []
|
|
72
|
+
final = _run({"after_s": 60, "note": "stretch"}, allowed_tools=["schedule_run", "read_file", "execute_command"],
|
|
73
|
+
handler=lambda run, plan: plans.append(plan) or {"automation_id": "auto-2"})
|
|
74
|
+
assert final.status == RunStatus.COMPLETED
|
|
75
|
+
assert plans[0]["allowed_tools"] == [] and plans[0]["kind"] == "note"
|
|
76
|
+
plans.clear()
|
|
77
|
+
_run({"after_s": 60, "task": "t"}, allowed_tools=["schedule_run", "read_file", "execute_command"],
|
|
78
|
+
handler=lambda run, plan: plans.append(plan) or {"automation_id": "auto-3"})
|
|
79
|
+
assert plans[0]["allowed_tools"] == ["execute_command", "read_file"]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@pytest.mark.basic
|
|
83
|
+
def test_schedule_run_outside_the_allowlist_is_refused_without_calling_the_host() -> None:
|
|
84
|
+
plans: List[Dict[str, Any]] = []
|
|
85
|
+
final = _run({"after_s": 60, "note": "x"}, allowed_tools=["read_file"], handler=lambda run, plan: plans.append(plan) or {"automation_id": "x"})
|
|
86
|
+
assert final.status == RunStatus.COMPLETED
|
|
87
|
+
assert plans == []
|
|
88
|
+
assert any("schedule_run" in m for m in _tool_messages(final))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@pytest.mark.basic
|
|
92
|
+
def test_schedule_run_passes_notify_and_notes_notify_by_default() -> None:
|
|
93
|
+
plans: List[Dict[str, Any]] = []
|
|
94
|
+
host = lambda run, plan: plans.append(plan) or {"automation_id": "a"} # noqa: E731
|
|
95
|
+
_run({"after_s": 60, "note": "drink water"}, allowed_tools=["schedule_run"], handler=host)
|
|
96
|
+
_run({"after_s": 60, "task": "t", "tools": []}, allowed_tools=["schedule_run"], handler=host)
|
|
97
|
+
_run({"after_s": 60, "task": "t", "tools": [], "notify": True}, allowed_tools=["schedule_run"], handler=host)
|
|
98
|
+
assert [p["notify"] for p in plans] == [True, False, True]
|
|
99
|
+
assert plans[0]["notify_title"] == "drink water"
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from abstractagent.adapters.react_runtime import create_react_workflow
|
|
6
|
+
from abstractagent.logic.react import ReActLogic
|
|
7
|
+
from abstractcore.tools import ToolDefinition
|
|
8
|
+
from abstractruntime.core.models import EffectType, RunStatus
|
|
9
|
+
from abstractruntime.core.runtime import EffectOutcome, Runtime
|
|
10
|
+
from abstractruntime.storage.in_memory import InMemoryLedgerStore, InMemoryRunStore
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.mark.parametrize("recover", [False, True])
|
|
14
|
+
def test_format_repair_is_bounded_and_never_executes_rejected_calls(recover):
|
|
15
|
+
requests: list[dict[str, Any]] = []
|
|
16
|
+
executed = []
|
|
17
|
+
|
|
18
|
+
def llm(run, effect, next_node):
|
|
19
|
+
requests.append(effect.payload)
|
|
20
|
+
if len(requests) > 1:
|
|
21
|
+
assert "nothing was executed" in str(effect.payload["messages"])
|
|
22
|
+
# The repair names no text format of its own (a native tool-calling
|
|
23
|
+
# provider must never be taught a text syntax): it points at the
|
|
24
|
+
# request's own instructions and lists the roster.
|
|
25
|
+
assert "<tool_call>" not in str(effect.payload["messages"])
|
|
26
|
+
assert "format and argument schemas supplied in the instructions" in str(effect.payload["messages"])
|
|
27
|
+
assert "Use only the available tools: web_search." in str(effect.payload["messages"])
|
|
28
|
+
if recover and len(requests) == 2:
|
|
29
|
+
return EffectOutcome.completed({"content": "", "tool_calls": [{"name": "web_search", "arguments": {"query": "test"}, "call_id": "one"}]})
|
|
30
|
+
if recover and len(requests) == 3:
|
|
31
|
+
return EffectOutcome.completed({"content": "Found it."})
|
|
32
|
+
return EffectOutcome.completed({"content": "Broken tool block", "metadata": {
|
|
33
|
+
"tool_call_error": {"code": "invalid_tool_syntax", "available_tools": ["web_search"]}}})
|
|
34
|
+
|
|
35
|
+
def tool(run, effect, next_node):
|
|
36
|
+
executed.extend(effect.payload["tool_calls"])
|
|
37
|
+
return EffectOutcome.completed({"mode": "executed", "results": [{"name": "web_search", "call_id": "one", "success": True, "output": "found"}]})
|
|
38
|
+
|
|
39
|
+
runtime = Runtime(run_store=InMemoryRunStore(), ledger_store=InMemoryLedgerStore(),
|
|
40
|
+
effect_handlers={EffectType.LLM_CALL: llm, EffectType.TOOL_CALLS: tool})
|
|
41
|
+
workflow = create_react_workflow(logic=ReActLogic(tools=[ToolDefinition(name="web_search", description="Search", parameters={"query": {"type": "string"}})]),
|
|
42
|
+
workflow_id="format-repair", provider="stub", model="stub", allowed_tools=["web_search"])
|
|
43
|
+
rid = runtime.start(workflow=workflow, vars={"context": {"task": "Search", "messages": []}, "_runtime": {"inbox": []}})
|
|
44
|
+
def drive():
|
|
45
|
+
for _ in range(80):
|
|
46
|
+
state = runtime.tick(workflow=workflow, run_id=rid, max_steps=1)
|
|
47
|
+
if state.status in (RunStatus.COMPLETED, RunStatus.FAILED):
|
|
48
|
+
return state
|
|
49
|
+
if recover:
|
|
50
|
+
assert drive().status == RunStatus.COMPLETED
|
|
51
|
+
else:
|
|
52
|
+
# GatewayRunner persists planning exceptions as FAILED (tick_exception).
|
|
53
|
+
with pytest.raises(RuntimeError, match="format repair failed"):
|
|
54
|
+
drive()
|
|
55
|
+
assert len(requests) == 3
|
|
56
|
+
assert len(executed) == (1 if recover else 0)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_format_repair_budget_is_per_turn_in_a_composed_conversation():
|
|
60
|
+
"""Two turns of one run (visit composition): each turn has two rejected formats and
|
|
61
|
+
still recovers. Without `reset_react_turn` clearing `tool_format_retries`, turn 2's
|
|
62
|
+
first rejection would raise at once (the budget was per conversation)."""
|
|
63
|
+
from abstractagent.adapters.react_runtime import reset_react_turn
|
|
64
|
+
from abstractruntime.core.models import StepPlan
|
|
65
|
+
from abstractruntime.core.spec import WorkflowSpec
|
|
66
|
+
|
|
67
|
+
bad = {"content": "Broken tool block", "metadata": {"tool_call_error": {"code": "invalid_tool_syntax", "available_tools": ["web_search"]}}}
|
|
68
|
+
script = [bad, bad, {"content": "Answer one."}, bad, bad, {"content": "Answer two."}]
|
|
69
|
+
calls = {"n": 0}
|
|
70
|
+
events: list[str] = []
|
|
71
|
+
answers: list[str] = []
|
|
72
|
+
|
|
73
|
+
def llm(run, effect, next_node):
|
|
74
|
+
i = calls["n"]
|
|
75
|
+
calls["n"] += 1
|
|
76
|
+
return EffectOutcome.completed(dict(script[min(i, len(script) - 1)]))
|
|
77
|
+
|
|
78
|
+
react = create_react_workflow(
|
|
79
|
+
logic=ReActLogic(tools=[ToolDefinition(name="web_search", description="Search", parameters={"query": {"type": "string"}})]),
|
|
80
|
+
workflow_id="format-repair-turns", provider="stub", model="stub", allowed_tools=["web_search"],
|
|
81
|
+
final_next_node="park", on_step=lambda step, data: events.append(step),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def park(run, ctx):
|
|
85
|
+
temp = run.vars.get("_temp") or {}
|
|
86
|
+
answers.append(str(temp.get("final_answer") or ""))
|
|
87
|
+
if len(answers) >= 2:
|
|
88
|
+
return StepPlan(node_id="park", complete_output={"turns": len(answers)})
|
|
89
|
+
reset_react_turn(run.vars)
|
|
90
|
+
run.vars["context"]["messages"].append({"role": "user", "content": "Second message"})
|
|
91
|
+
return StepPlan(node_id="park", next_node="reason")
|
|
92
|
+
|
|
93
|
+
nodes = dict(react.nodes)
|
|
94
|
+
nodes["park"] = park
|
|
95
|
+
workflow = WorkflowSpec(workflow_id=react.workflow_id, entry_node=react.entry_node, nodes=nodes)
|
|
96
|
+
runtime = Runtime(run_store=InMemoryRunStore(), ledger_store=InMemoryLedgerStore(), effect_handlers={EffectType.LLM_CALL: llm})
|
|
97
|
+
rid = runtime.start(workflow=workflow, vars={"context": {"task": "First message", "messages": []}, "_runtime": {"inbox": []}})
|
|
98
|
+
state = None
|
|
99
|
+
for _ in range(120):
|
|
100
|
+
state = runtime.tick(workflow=workflow, run_id=rid, max_steps=1)
|
|
101
|
+
if state.status in (RunStatus.COMPLETED, RunStatus.FAILED):
|
|
102
|
+
break
|
|
103
|
+
assert state is not None and state.status == RunStatus.COMPLETED, state
|
|
104
|
+
assert answers == ["Answer one.", "Answer two."]
|
|
105
|
+
assert events.count("parse_retry_tool_format") == 4
|
|
106
|
+
assert calls["n"] == 6
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
abstractcore[tools]>=2.25.0
|
|
2
|
-
abstractruntime>=0.7.0
|
|
3
|
-
|
|
4
|
-
[apple]
|
|
5
|
-
abstractcore[all-apple]>=2.25.0
|
|
6
|
-
abstractruntime[apple]>=0.7.0
|
|
7
|
-
|
|
8
|
-
[dev]
|
|
9
|
-
pytest>=7.0
|
|
10
|
-
|
|
11
|
-
[gpu]
|
|
12
|
-
abstractcore[all-gpu]>=2.25.0
|
|
13
|
-
abstractruntime[gpu]>=0.7.0
|
|
14
|
-
|
|
15
|
-
[test]
|
|
16
|
-
pytest>=7.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/adapters/generation_params.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/adapters/native_loop_registry.py
RENAMED
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/adapters/read_orchestration.py
RENAMED
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/adapters/tool_failure_hints.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent/scripts/lmstudio_tool_eval.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.4.0}/src/abstractagent.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_builtin_tool_specs_optional_defaults.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_delegate_session_route_composition.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_guidance_durable_transcript.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_loop_aborted_generation_retry.py
RENAMED
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_loop_context_transcript_levels.py
RENAMED
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_loop_side_effect_dedupe_comms.py
RENAMED
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_react_loop_truncated_output_retry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_run_start_seeds_runtime_provider_model.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.4.0}/tests/test_tool_observe_emits_result_for_tui.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|