AbstractAgent 0.3.16__tar.gz → 0.3.18__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.16/src/abstractagent.egg-info → abstractagent-0.3.18}/PKG-INFO +14 -11
- {abstractagent-0.3.16 → abstractagent-0.3.18}/README.md +7 -4
- {abstractagent-0.3.16 → abstractagent-0.3.18}/pyproject.toml +7 -7
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/adapters/react_runtime.py +28 -2
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/sandbox/local.py +16 -2
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/tools/code_execution.py +42 -5
- {abstractagent-0.3.16 → abstractagent-0.3.18/src/abstractagent.egg-info}/PKG-INFO +14 -11
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent.egg-info/SOURCES.txt +2 -0
- abstractagent-0.3.18/src/abstractagent.egg-info/requires.txt +16 -0
- abstractagent-0.3.18/tests/test_execute_python_sandbox.py +121 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_history_monster_clamp.py +28 -15
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_packaging_profiles.py +6 -6
- abstractagent-0.3.18/tests/test_react_history_window.py +64 -0
- abstractagent-0.3.16/src/abstractagent.egg-info/requires.txt +0 -16
- {abstractagent-0.3.16 → abstractagent-0.3.18}/LICENSE +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/setup.cfg +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/__init__.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/adapters/__init__.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/adapters/codeact_runtime.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/adapters/emit_inventory.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/adapters/generation_params.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/adapters/loop_hooks.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/adapters/media.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/adapters/memact_runtime.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/adapters/native_loop_registry.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/adapters/progress.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/adapters/read_orchestration.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/adapters/tool_allowlist.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/adapters/tool_failure_hints.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/adapters/transcripts.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/agents/__init__.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/agents/base.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/agents/codeact.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/agents/defaults.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/agents/memact.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/agents/react.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/agents/task_reset.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/agents/unattended.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/logic/__init__.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/logic/builtins.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/logic/codeact.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/logic/memact.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/logic/react.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/logic/types.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/metrics/__init__.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/metrics/prefix_reuse.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/repl.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/sandbox/__init__.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/sandbox/interface.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/scripts/__init__.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/scripts/lmstudio_tool_eval.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/tools/__init__.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/tools/self_improve.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent.egg-info/dependency_links.txt +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent.egg-info/entry_points.txt +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent.egg-info/top_level.txt +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_active_context_policy_adoption.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_adversary_hardening_2026_07_13.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_agent_plan_review_modes.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_agent_speculation_inheritance.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_agent_stream_inheritance.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_agents_integration_llm.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_base_agent_trace_accessors.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_batching_prompt_rule.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_budget_triage_tail.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_builtin_tool_specs_optional_defaults.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_c2447_visit_lane_honesty.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_claim_0029_residue_2026_07_14.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_codeact_logic.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_codeact_runtime_verifier_policy.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_delegate_session_route_composition.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_emit_inventory_drift.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_gated_remainder_wave_2026_07_14.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_generation_params_media_policies.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_improvement_wave_2026_07_13.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_llm_call_payload_requires_prompt_or_messages.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_lmstudio_tool_eval_metrics.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_loop_hooks.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_loop_hooks_0026_follow_ups.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_memact_codeact_guidance_durable_transcript.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_memact_session_memory.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_memact_workflow.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_memory_recall_tool.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_native_loop_registry.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_open_attachment_tool_available.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_operator_conclude.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_optimal_wave_batch2_2026_07_13.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_parse_prompt_cache_capture.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_prefix_reuse_metric.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_progress_detection.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_act_only_observation.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_active_attachments_disable_tools_first_iteration.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_adapter_forwards_context_attachments_media.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_delegate_agent_tool.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_election_fence_conformance.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_entity_dress_conformance.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_guidance_durable_transcript.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_logic.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_loop_aborted_generation_retry.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_loop_context_transcript_levels.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_loop_followthrough_retry.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_loop_side_effect_dedupe_comms.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_loop_truncated_output_retry.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_max_iterations_concludes.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_phase_grant_boundary.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_planish_heuristic.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_prompt_prefix_stability.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_sight_lane_media_fold.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_turn_byte_stability.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_verifier_and_plan.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_visit_composition.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_visit_merge.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_workflow_system_prompt_and_tools_override.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_workflow_toolset_refresh.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_read_orchestration_guidance.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_reasoning_first_citizen_agent.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_review_failure_containment.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_run_start_seeds_runtime_provider_model.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_session_memory.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_sibling_loop_defects_batch.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_sibling_strict_transcripts_0011.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_stuck_streak_termination.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_tool_failure_hints.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_tool_loop_prefix_extension_a3.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_tool_observe_emits_result_for_tui.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_tools_editing.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_unseeded_render_limits.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_verifier_execution_preference.py +0 -0
- {abstractagent-0.3.16 → abstractagent-0.3.18}/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.
|
|
3
|
+
Version: 0.3.18
|
|
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.25.0
|
|
9
|
+
Requires-Dist: abstractruntime>=0.7.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.25.0; extra == "apple"
|
|
12
|
+
Requires-Dist: abstractruntime[apple]>=0.7.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.25.0; extra == "gpu"
|
|
15
|
+
Requires-Dist: abstractruntime[gpu]>=0.7.0; extra == "gpu"
|
|
16
16
|
Provides-Extra: dev
|
|
17
17
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
18
18
|
Provides-Extra: test
|
|
@@ -105,10 +105,13 @@ Native Python hardware profile cascades are available for deployment manifests:
|
|
|
105
105
|
AbstractCore and AbstractRuntime profiles; AbstractAgent itself remains
|
|
106
106
|
provider/runtime agnostic.
|
|
107
107
|
|
|
108
|
-
AbstractAgent 0.3.
|
|
109
|
-
`AbstractRuntime>=0.
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
AbstractAgent 0.3.17 requires `abstractcore[tools]>=2.13.41` and
|
|
109
|
+
`AbstractRuntime>=0.7.0` (the runtime release with the 50k-token history
|
|
110
|
+
window, which ReAct applies to its requests when a host sets
|
|
111
|
+
`_runtime.history_window_tokens`, as the entity visit does; 0.5.0 brought live
|
|
112
|
+
token streaming, on top of turn grounding, in-flight cancellation and
|
|
113
|
+
speculation inheritance). AbstractRuntime 0.7.0 itself requires AbstractCore
|
|
114
|
+
2.18.0 or newer.
|
|
112
115
|
|
|
113
116
|
Note: the repository may be ahead of the latest published PyPI release. To verify what you installed:
|
|
114
117
|
|
|
@@ -84,10 +84,13 @@ Native Python hardware profile cascades are available for deployment manifests:
|
|
|
84
84
|
AbstractCore and AbstractRuntime profiles; AbstractAgent itself remains
|
|
85
85
|
provider/runtime agnostic.
|
|
86
86
|
|
|
87
|
-
AbstractAgent 0.3.
|
|
88
|
-
`AbstractRuntime>=0.
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
AbstractAgent 0.3.17 requires `abstractcore[tools]>=2.13.41` and
|
|
88
|
+
`AbstractRuntime>=0.7.0` (the runtime release with the 50k-token history
|
|
89
|
+
window, which ReAct applies to its requests when a host sets
|
|
90
|
+
`_runtime.history_window_tokens`, as the entity visit does; 0.5.0 brought live
|
|
91
|
+
token streaming, on top of turn grounding, in-flight cancellation and
|
|
92
|
+
speculation inheritance). AbstractRuntime 0.7.0 itself requires AbstractCore
|
|
93
|
+
2.18.0 or newer.
|
|
91
94
|
|
|
92
95
|
Note: the repository may be ahead of the latest published PyPI release. To verify what you installed:
|
|
93
96
|
|
|
@@ -4,23 +4,23 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "abstractagent"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.18"
|
|
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.25.0",
|
|
13
|
+
"abstractruntime>=0.7.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.25.0",
|
|
19
|
+
"abstractruntime[apple]>=0.7.0",
|
|
20
20
|
]
|
|
21
21
|
gpu = [
|
|
22
|
-
"abstractcore[all-gpu]>=2.
|
|
23
|
-
"abstractruntime[gpu]>=0.
|
|
22
|
+
"abstractcore[all-gpu]>=2.25.0",
|
|
23
|
+
"abstractruntime[gpu]>=0.7.0",
|
|
24
24
|
]
|
|
25
25
|
dev = [
|
|
26
26
|
"pytest>=7.0",
|
|
@@ -24,6 +24,7 @@ from typing import Any, Callable, Dict, List, Optional
|
|
|
24
24
|
from abstractcore.tools import ToolCall
|
|
25
25
|
from abstractruntime import Effect, EffectType, RunState, StepPlan, WorkflowSpec
|
|
26
26
|
from abstractruntime.core.vars import ensure_limits, ensure_namespaces
|
|
27
|
+
from abstractruntime.session_history import window_transcript
|
|
27
28
|
from abstractruntime.turn_grounding import stamp_user_turn_grounding, strip_turn_grounding
|
|
28
29
|
|
|
29
30
|
from .generation_params import (
|
|
@@ -1659,6 +1660,31 @@ def create_react_workflow(
|
|
|
1659
1660
|
out.append(d)
|
|
1660
1661
|
return out
|
|
1661
1662
|
|
|
1663
|
+
def _history_window_view(messages: List[Dict[str, Any]], runtime_ns: Any) -> List[Dict[str, Any]]:
|
|
1664
|
+
"""The transcript a model request carries when the host asks for THE
|
|
1665
|
+
runtime history window (`_runtime.history_window_tokens`, set by the
|
|
1666
|
+
entity visit's BRIDGE; operator ruling 2026-09-28, ADR-0026).
|
|
1667
|
+
|
|
1668
|
+
`abstractruntime.session_history.window_transcript`: the newest whole
|
|
1669
|
+
turns up to that many tokens (a tool result stays with its turn), a
|
|
1670
|
+
labeled #TRUNCATION notice when older turns were dropped. The turn in
|
|
1671
|
+
progress starts at `_runtime.history_window_turn_start` (set by the
|
|
1672
|
+
host) and is kept whole, so an ask_user answer or operator guidance
|
|
1673
|
+
inside it never splits off the turn's own question and tool results. The durable
|
|
1674
|
+
`context.messages` is never touched — only this request's view — and
|
|
1675
|
+
the window's receipt is recorded at `_runtime.session_history`.
|
|
1676
|
+
Unset = the whole transcript (the plain task lane's full-context policy).
|
|
1677
|
+
"""
|
|
1678
|
+
if not isinstance(runtime_ns, dict) or runtime_ns.get("history_window_tokens") is None:
|
|
1679
|
+
return messages
|
|
1680
|
+
window = window_transcript(
|
|
1681
|
+
messages,
|
|
1682
|
+
max_tokens=runtime_ns["history_window_tokens"],
|
|
1683
|
+
current_turn_start=runtime_ns.get("history_window_turn_start"),
|
|
1684
|
+
)
|
|
1685
|
+
runtime_ns["session_history"] = dict(window.report)
|
|
1686
|
+
return list(window)
|
|
1687
|
+
|
|
1662
1688
|
def _sanitize_llm_messages(
|
|
1663
1689
|
messages: Any, *, limits: Optional[Dict[str, Any]] = None
|
|
1664
1690
|
) -> List[Dict[str, Any]]:
|
|
@@ -1874,7 +1900,7 @@ def create_react_workflow(
|
|
|
1874
1900
|
|
|
1875
1901
|
|
|
1876
1902
|
payload: Dict[str, Any] = {"prompt": ""}
|
|
1877
|
-
sanitized_messages = _sanitize_llm_messages(messages_view, limits=limits)
|
|
1903
|
+
sanitized_messages = _sanitize_llm_messages(_history_window_view(messages_view, runtime_ns), limits=limits)
|
|
1878
1904
|
if sanitized_messages:
|
|
1879
1905
|
payload["messages"] = sanitized_messages
|
|
1880
1906
|
else:
|
|
@@ -3765,7 +3791,7 @@ def create_react_workflow(
|
|
|
3765
3791
|
)
|
|
3766
3792
|
|
|
3767
3793
|
payload: Dict[str, Any] = {"prompt": ""}
|
|
3768
|
-
sanitized_messages = _sanitize_llm_messages(messages_view, limits=limits)
|
|
3794
|
+
sanitized_messages = _sanitize_llm_messages(_history_window_view(messages_view, runtime_ns), limits=limits)
|
|
3769
3795
|
if sanitized_messages:
|
|
3770
3796
|
payload["messages"] = sanitized_messages
|
|
3771
3797
|
else:
|
|
@@ -11,7 +11,7 @@ import os
|
|
|
11
11
|
import subprocess
|
|
12
12
|
import sys
|
|
13
13
|
import time
|
|
14
|
-
from typing import Optional
|
|
14
|
+
from typing import Any, Optional
|
|
15
15
|
|
|
16
16
|
from .interface import ExecutionResult
|
|
17
17
|
|
|
@@ -22,9 +22,14 @@ class LocalSandbox:
|
|
|
22
22
|
*,
|
|
23
23
|
cwd: Optional[str] = None,
|
|
24
24
|
python_executable: Optional[str] = None,
|
|
25
|
+
command_sandbox: Any = None,
|
|
25
26
|
):
|
|
27
|
+
"""`command_sandbox`: an AbstractCore `abstractcore.tools.sandbox.Sandbox` (round 12). When
|
|
28
|
+
given, the interpreter runs inside it (OS sandbox bound to the run's workspaces, the host's
|
|
29
|
+
scrubbed environment, a private TMPDIR); the caller refuses before when it `refuses`."""
|
|
26
30
|
self._cwd = cwd or os.getcwd()
|
|
27
31
|
self._python = python_executable or sys.executable
|
|
32
|
+
self._command_sandbox = command_sandbox
|
|
28
33
|
|
|
29
34
|
def reset(self) -> None:
|
|
30
35
|
# Stateless sandbox (new subprocess per call).
|
|
@@ -47,10 +52,19 @@ class LocalSandbox:
|
|
|
47
52
|
except (TypeError, ValueError):
|
|
48
53
|
t = 0.0
|
|
49
54
|
eff_timeout = t if t > 0 else None
|
|
55
|
+
argv = [self._python, "-c", code]
|
|
56
|
+
env = None
|
|
57
|
+
box = self._command_sandbox
|
|
58
|
+
if box is not None:
|
|
59
|
+
if box.kind == "unsandboxed":
|
|
60
|
+
env = box.env_for()
|
|
61
|
+
else:
|
|
62
|
+
argv, env = box.wrap(argv, self._cwd)
|
|
50
63
|
try:
|
|
51
64
|
completed = subprocess.run(
|
|
52
|
-
|
|
65
|
+
argv,
|
|
53
66
|
cwd=self._cwd,
|
|
67
|
+
env=env,
|
|
54
68
|
capture_output=True,
|
|
55
69
|
text=True,
|
|
56
70
|
timeout=eff_timeout,
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
5
7
|
from abstractcore.tools import tool
|
|
6
8
|
|
|
7
9
|
|
|
@@ -25,16 +27,30 @@ def _bound_output(text: str, *, max_chars: int) -> str:
|
|
|
25
27
|
)
|
|
26
28
|
|
|
27
29
|
|
|
30
|
+
# Round 12: the one sentence when the installed AbstractCore has no command sandbox.
|
|
31
|
+
NO_CORE_SANDBOX = (
|
|
32
|
+
"Commands are refused: the installed AbstractCore has no command sandbox "
|
|
33
|
+
"(abstractcore.tools.sandbox, AbstractCore 2.25.0 or newer)."
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
28
37
|
@tool(
|
|
29
38
|
name="execute_python",
|
|
30
|
-
description="Execute a Python snippet in a subprocess
|
|
39
|
+
description="Execute a Python snippet in a subprocess, sandboxed to this run's workspaces. Returns stdout/stderr/exit_code in full.",
|
|
31
40
|
when_to_use="When you need to compute, inspect files, or transform data using Python code",
|
|
41
|
+
# Stamped by the host (AbstractRuntime) with the run's workspace set; never model-facing.
|
|
42
|
+
hide_args=["_sandbox"],
|
|
32
43
|
)
|
|
33
|
-
def execute_python(code: str, timeout_s: float = 0.0, max_output_chars: int = 0) -> dict:
|
|
44
|
+
def execute_python(code: str, timeout_s: float = 0.0, max_output_chars: int = 0, _sandbox: Optional[dict] = None) -> dict:
|
|
34
45
|
"""Execute Python code in a local subprocess.
|
|
35
46
|
|
|
36
47
|
Notes:
|
|
37
|
-
-
|
|
48
|
+
- Round 12: when the host stamps `_sandbox` (the run's effective workspace set), the
|
|
49
|
+
interpreter runs inside AbstractCore's OS sandbox (macOS sandbox-exec, Linux bwrap/Landlock),
|
|
50
|
+
starting in the run's private workspace with the host's scrubbed environment. No sandbox
|
|
51
|
+
on the host (or an AbstractCore without one) = refused (`error` carries the sentence,
|
|
52
|
+
nothing runs). A host that configured AbstractCore's command policy refuses an unstamped
|
|
53
|
+
call. Library use without a host policy runs as before.
|
|
38
54
|
- `timeout_s <= 0` (the default) means NO timeout (ADR-0027: no low default
|
|
39
55
|
timeouts on correctness-critical paths — a 10s default silently killed
|
|
40
56
|
legitimate long computations and reported them as errors). Pass a positive
|
|
@@ -48,7 +64,24 @@ def execute_python(code: str, timeout_s: float = 0.0, max_output_chars: int = 0)
|
|
|
48
64
|
|
|
49
65
|
from ..sandbox.local import LocalSandbox
|
|
50
66
|
|
|
51
|
-
|
|
67
|
+
try:
|
|
68
|
+
from abstractcore.tools import sandbox as core_sandbox
|
|
69
|
+
except ImportError:
|
|
70
|
+
return {"stdout": "", "stderr": "", "exit_code": 126, "duration_ms": 0.0, "error": NO_CORE_SANDBOX, "success": False}
|
|
71
|
+
box = core_sandbox.sandbox_for_tool_call(_sandbox)
|
|
72
|
+
if box.refuses:
|
|
73
|
+
return {
|
|
74
|
+
"stdout": "",
|
|
75
|
+
"stderr": "",
|
|
76
|
+
"exit_code": 126,
|
|
77
|
+
"duration_ms": 0.0,
|
|
78
|
+
"error": box.refusal(),
|
|
79
|
+
"success": False,
|
|
80
|
+
"sandbox": box.describe(),
|
|
81
|
+
"sandbox_line": box.rendered_line(),
|
|
82
|
+
}
|
|
83
|
+
bound = box if (box.spec is not None or box.kind != core_sandbox.KIND_UNSANDBOXED) else None
|
|
84
|
+
sandbox = LocalSandbox(cwd=box.spec.private_workspace if box.spec is not None else None, command_sandbox=bound)
|
|
52
85
|
#[WARNING:TIMEOUT] caller-supplied only; <= 0 = unlimited (ADR-0027)
|
|
53
86
|
timeout = float(timeout_s)
|
|
54
87
|
result = sandbox.execute(code, timeout_s=timeout if timeout > 0 else None)
|
|
@@ -56,11 +89,15 @@ def execute_python(code: str, timeout_s: float = 0.0, max_output_chars: int = 0)
|
|
|
56
89
|
cap = int(max_output_chars)
|
|
57
90
|
except (TypeError, ValueError):
|
|
58
91
|
cap = 0
|
|
59
|
-
|
|
92
|
+
out = {
|
|
60
93
|
"stdout": _bound_output(result.stdout, max_chars=cap),
|
|
61
94
|
"stderr": _bound_output(result.stderr, max_chars=cap),
|
|
62
95
|
"exit_code": int(result.exit_code),
|
|
63
96
|
"duration_ms": float(result.duration_ms),
|
|
64
97
|
"error": result.error,
|
|
65
98
|
}
|
|
99
|
+
if bound is not None:
|
|
100
|
+
out["sandbox"] = bound.describe()
|
|
101
|
+
out["sandbox_line"] = bound.rendered_line()
|
|
102
|
+
return out
|
|
66
103
|
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: abstractagent
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.18
|
|
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.25.0
|
|
9
|
+
Requires-Dist: abstractruntime>=0.7.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.25.0; extra == "apple"
|
|
12
|
+
Requires-Dist: abstractruntime[apple]>=0.7.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.25.0; extra == "gpu"
|
|
15
|
+
Requires-Dist: abstractruntime[gpu]>=0.7.0; extra == "gpu"
|
|
16
16
|
Provides-Extra: dev
|
|
17
17
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
18
18
|
Provides-Extra: test
|
|
@@ -105,10 +105,13 @@ Native Python hardware profile cascades are available for deployment manifests:
|
|
|
105
105
|
AbstractCore and AbstractRuntime profiles; AbstractAgent itself remains
|
|
106
106
|
provider/runtime agnostic.
|
|
107
107
|
|
|
108
|
-
AbstractAgent 0.3.
|
|
109
|
-
`AbstractRuntime>=0.
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
AbstractAgent 0.3.17 requires `abstractcore[tools]>=2.13.41` and
|
|
109
|
+
`AbstractRuntime>=0.7.0` (the runtime release with the 50k-token history
|
|
110
|
+
window, which ReAct applies to its requests when a host sets
|
|
111
|
+
`_runtime.history_window_tokens`, as the entity visit does; 0.5.0 brought live
|
|
112
|
+
token streaming, on top of turn grounding, in-flight cancellation and
|
|
113
|
+
speculation inheritance). AbstractRuntime 0.7.0 itself requires AbstractCore
|
|
114
|
+
2.18.0 or newer.
|
|
112
115
|
|
|
113
116
|
Note: the repository may be ahead of the latest published PyPI release. To verify what you installed:
|
|
114
117
|
|
|
@@ -63,6 +63,7 @@ tests/test_codeact_logic.py
|
|
|
63
63
|
tests/test_codeact_runtime_verifier_policy.py
|
|
64
64
|
tests/test_delegate_session_route_composition.py
|
|
65
65
|
tests/test_emit_inventory_drift.py
|
|
66
|
+
tests/test_execute_python_sandbox.py
|
|
66
67
|
tests/test_gated_remainder_wave_2026_07_14.py
|
|
67
68
|
tests/test_generation_params_media_policies.py
|
|
68
69
|
tests/test_history_monster_clamp.py
|
|
@@ -90,6 +91,7 @@ tests/test_react_delegate_agent_tool.py
|
|
|
90
91
|
tests/test_react_election_fence_conformance.py
|
|
91
92
|
tests/test_react_entity_dress_conformance.py
|
|
92
93
|
tests/test_react_guidance_durable_transcript.py
|
|
94
|
+
tests/test_react_history_window.py
|
|
93
95
|
tests/test_react_logic.py
|
|
94
96
|
tests/test_react_loop_aborted_generation_retry.py
|
|
95
97
|
tests/test_react_loop_context_transcript_levels.py
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Round 12: execute_python runs inside AbstractCore's OS command sandbox.
|
|
2
|
+
|
|
3
|
+
Real macOS tests (sandbox-exec) with scratch folders and markers; the runtime path
|
|
4
|
+
(AbstractRuntime stamps `_sandbox` on the CodeAct tool call) is exercised end to end."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
from abstractcore.tools import sandbox as core_sandbox
|
|
15
|
+
from abstractcore.tools.sandbox import SandboxRow, SandboxSpec
|
|
16
|
+
|
|
17
|
+
from abstractagent.tools.code_execution import NO_CORE_SANDBOX, execute_python
|
|
18
|
+
|
|
19
|
+
MARK = "R12-AGENT-REFUSED-MARKER"
|
|
20
|
+
|
|
21
|
+
macos_only = pytest.mark.skipif(
|
|
22
|
+
sys.platform != "darwin" or not os.access(core_sandbox.SANDBOX_EXEC, os.X_OK),
|
|
23
|
+
reason="real sandbox test: this host is not macOS with /usr/bin/sandbox-exec (Linux backends are tested in abstractcore)",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.fixture(autouse=True)
|
|
28
|
+
def _fresh_host():
|
|
29
|
+
core_sandbox._reset_host_for_tests()
|
|
30
|
+
yield
|
|
31
|
+
core_sandbox._reset_host_for_tests()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@pytest.fixture
|
|
35
|
+
def t(tmp_path, monkeypatch):
|
|
36
|
+
root = Path(os.path.realpath(tmp_path))
|
|
37
|
+
for d in ("home", "priv", "refused", "rw"):
|
|
38
|
+
(root / d).mkdir()
|
|
39
|
+
(root / "refused/secret.txt").write_text(MARK)
|
|
40
|
+
monkeypatch.setenv("HOME", str(root / "home"))
|
|
41
|
+
return root
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _stamp(t: Path, refused=True):
|
|
45
|
+
return SandboxSpec(
|
|
46
|
+
private_workspace=str(t / "priv"),
|
|
47
|
+
posture="any_except_denied",
|
|
48
|
+
allowed=(SandboxRow(str(t / "rw"), "rw"),),
|
|
49
|
+
refused=(str(t / "refused"),) if refused else (),
|
|
50
|
+
).to_stamp()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_sandbox_arg_is_hidden_from_the_model():
|
|
54
|
+
assert "_sandbox" not in execute_python._tool_definition.parameters
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@macos_only
|
|
58
|
+
def test_refused_path_is_unreadable_from_python(t):
|
|
59
|
+
code = f"print(open({str(t / 'refused/secret.txt')!r}).read())"
|
|
60
|
+
res = execute_python(code, _sandbox=_stamp(t))
|
|
61
|
+
assert MARK not in res["stdout"] and res["exit_code"] != 0
|
|
62
|
+
assert "Operation not permitted" in res["stderr"]
|
|
63
|
+
assert res["sandbox"]["kind"] == "macos-sandbox-exec" and res["sandbox_line"] == "Sandbox: macOS sandbox-exec"
|
|
64
|
+
ok = execute_python(code, _sandbox=_stamp(t, refused=False))
|
|
65
|
+
assert MARK in ok["stdout"] # positive control
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@macos_only
|
|
69
|
+
def test_starts_in_the_private_workspace_with_a_private_tmpdir(t):
|
|
70
|
+
res = execute_python("import os,tempfile;open('w.txt','w').write('x');print(os.getcwd());print(tempfile.gettempdir())", _sandbox=_stamp(t))
|
|
71
|
+
assert res["stdout"].split() == [str(t / "priv"), str(t / "priv/.tmp")]
|
|
72
|
+
assert (t / "priv/w.txt").exists()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_fail_closed_without_a_sandbox(t, monkeypatch):
|
|
76
|
+
monkeypatch.setattr(core_sandbox, "host_sandbox_kind", lambda posture="allowed_only": core_sandbox.KIND_NONE)
|
|
77
|
+
res = execute_python(f"open({str(t / 'priv/ran')!r},'w')", _sandbox=_stamp(t))
|
|
78
|
+
assert res["success"] is False and res["error"].startswith("Commands are not sandboxed on this gateway host")
|
|
79
|
+
assert not (t / "priv/ran").exists()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_fail_closed_without_core_sandbox_module(t, monkeypatch):
|
|
83
|
+
import abstractcore.tools as core_tools
|
|
84
|
+
|
|
85
|
+
monkeypatch.setitem(sys.modules, "abstractcore.tools.sandbox", None)
|
|
86
|
+
monkeypatch.delattr(core_tools, "sandbox", raising=False)
|
|
87
|
+
res = execute_python(f"open({str(t / 'priv/ran')!r},'w')", _sandbox=_stamp(t))
|
|
88
|
+
assert res["success"] is False and res["error"] == NO_CORE_SANDBOX
|
|
89
|
+
assert not (t / "priv/ran").exists()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_configured_host_refuses_unstamped_call(t):
|
|
93
|
+
core_sandbox.configure_host(env={"PATH": "/usr/bin:/bin"})
|
|
94
|
+
res = execute_python(f"open({str(t / 'priv/ran')!r},'w')")
|
|
95
|
+
assert res["success"] is False and res["error"] == core_sandbox.REFUSED_NO_SPEC
|
|
96
|
+
assert not (t / "priv/ran").exists()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@macos_only
|
|
100
|
+
def test_codeact_tool_call_through_the_runtime_is_sandboxed(t):
|
|
101
|
+
"""A CodeAct-style TOOL_CALLS effect: the runtime stamps the run's workspaces on
|
|
102
|
+
execute_python; reading a refused workspace fails inside the sandbox."""
|
|
103
|
+
from abstractruntime.core.models import Effect, EffectType, RunState
|
|
104
|
+
from abstractruntime.integrations.abstractcore.effect_handlers import make_tool_calls_handler
|
|
105
|
+
from abstractruntime.integrations.abstractcore.tool_executor import MappingToolExecutor
|
|
106
|
+
|
|
107
|
+
handler = make_tool_calls_handler(tools=MappingToolExecutor.from_tools([execute_python]))
|
|
108
|
+
run_vars = {
|
|
109
|
+
"workspace_root": str(t / "priv"),
|
|
110
|
+
"workspace_access_mode": "all_except_ignored",
|
|
111
|
+
"workspace_allowed_paths": [str(t / "rw")],
|
|
112
|
+
"workspace_ignored_paths": str(t / "refused"),
|
|
113
|
+
}
|
|
114
|
+
run = RunState.new(workflow_id="wf", entry_node="n", session_id="s", vars=run_vars)
|
|
115
|
+
code = f"print(open({str(t / 'refused/secret.txt')!r}).read())"
|
|
116
|
+
effect = Effect(type=EffectType.TOOL_CALLS, payload={"tool_calls": [{"call_id": "c1", "name": "execute_python", "arguments": {"code": code, "_sandbox": {"private_workspace": "/", "posture": "any_except_denied"}}}]})
|
|
117
|
+
outcome = handler(run, effect, None)
|
|
118
|
+
res = outcome.result["results"][0]
|
|
119
|
+
out = res["output"]
|
|
120
|
+
assert MARK not in str(out)
|
|
121
|
+
assert out["sandbox"]["kind"] == "macos-sandbox-exec" and out["sandbox"]["refused"] == [str(t / "refused")]
|
|
@@ -241,18 +241,23 @@ def _poison_tools(run: Any, effect: Effect, dnn: Any = None) -> EffectOutcome:
|
|
|
241
241
|
|
|
242
242
|
def test_poisoned_visit_transcript_recovers_on_the_next_turn(tmp_path: Path) -> None:
|
|
243
243
|
"""End-to-end on the REAL merged lane (the incident's lane): turn 1
|
|
244
|
-
poisons the durable transcript with a ~495k tool message
|
|
245
|
-
|
|
246
|
-
|
|
244
|
+
poisons the durable transcript with a ~495k tool message. Within turn 1
|
|
245
|
+
the result rides the next call clamped to OVERSIZED_MESSAGE_CLAMP_CHARS
|
|
246
|
+
(the newest turn is kept whole by the window). From turn 2 on, the visit
|
|
247
|
+
BRIDGE's history window (`_runtime.history_window_tokens`, runtime 0.7,
|
|
248
|
+
ADR-0026) drops the poisoned turn whole — call, result and reply — because
|
|
249
|
+
it no longer fits beside a newer turn: the request is the labeled notice
|
|
250
|
+
and the new words. The session recovers with NO manual surgery while the
|
|
251
|
+
durable record keeps the full truth."""
|
|
247
252
|
pytest.importorskip("abstractmemory")
|
|
248
253
|
from abstractruntime.identity.entity_runtime import open_entity_runtime
|
|
249
254
|
from abstractruntime.identity.visit_workflow import (
|
|
250
255
|
HARVEST_NODE,
|
|
251
|
-
VISIT_HISTORY_TOOL_RESULT_CAP_CHARS,
|
|
252
256
|
VISITOR_WAIT_KEY,
|
|
253
257
|
ReactMiddle,
|
|
254
258
|
build_visit_workflow,
|
|
255
259
|
)
|
|
260
|
+
from abstractruntime.turn_grounding import strip_turn_grounding
|
|
256
261
|
|
|
257
262
|
home_dir = _make_home(tmp_path)
|
|
258
263
|
llm = _ScriptedLLM([
|
|
@@ -312,20 +317,28 @@ def test_poisoned_visit_transcript_recovers_on_the_next_turn(tmp_path: Path) ->
|
|
|
312
317
|
assert state.status == RunStatus.WAITING
|
|
313
318
|
assert len(llm.calls) == 3
|
|
314
319
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
assert len(
|
|
318
|
-
clamped =
|
|
319
|
-
assert len(clamped) <=
|
|
320
|
+
# Within turn 1, the call after the read carried the clamped stub.
|
|
321
|
+
in_turn_tool = [m for m in llm.calls[1]["messages"] if m.get("role") == "tool"]
|
|
322
|
+
assert len(in_turn_tool) == 1
|
|
323
|
+
clamped = in_turn_tool[0]["content"]
|
|
324
|
+
assert len(clamped) <= OVERSIZED_MESSAGE_CLAMP_CHARS + 200
|
|
320
325
|
assert clamped.startswith("[read_file]: --- shared/Screenshot_2026-08-01"), "head kept - labeled, never dropped"
|
|
321
326
|
assert "chars elided: oversized tool result" in clamped
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
assert
|
|
326
|
-
|
|
327
|
+
|
|
328
|
+
# Turn 2: the poisoned turn is out of the request, whole, and says so.
|
|
329
|
+
wire = llm.calls[2]["messages"]
|
|
330
|
+
assert [m for m in wire if m.get("role") == "tool"] == []
|
|
331
|
+
assert not any(m.get("tool_calls") for m in wire)
|
|
332
|
+
assert len(wire) == 1 and wire[0]["role"] == "user"
|
|
333
|
+
# One head envelope (the turn's own), then the labeled drop notice.
|
|
334
|
+
assert wire[0]["content"].count("<runtime_metadata>") == 1
|
|
335
|
+
assert strip_turn_grounding(wire[0]["content"]).startswith("[#TRUNCATION: 4 earlier message(s)")
|
|
336
|
+
assert "Are you still with me?" in wire[0]["content"]
|
|
337
|
+
report = ert.runtime.get_state(run_id).vars["_runtime"]["session_history"]
|
|
338
|
+
assert report["dropped_messages"] == 4 and report["replayed_messages"] == 1
|
|
339
|
+
# The whole request shrank from monster-class to one short message.
|
|
327
340
|
total = sum(len(str(m.get("content") or "")) for m in wire)
|
|
328
|
-
assert total <
|
|
341
|
+
assert total < 2_000, f"recovered payload still huge: {total}"
|
|
329
342
|
|
|
330
343
|
# And the durable record STILL holds the full truth (ADR-0026).
|
|
331
344
|
stored = ert.runtime.get_state(run_id).vars["context"]["messages"]
|
|
@@ -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.25.0" in deps
|
|
22
|
+
assert "abstractruntime>=0.7.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.25.0",
|
|
30
|
+
"abstractruntime[apple]>=0.7.0",
|
|
31
31
|
}
|
|
32
32
|
assert set(extras["gpu"]) == {
|
|
33
|
-
"abstractcore[all-gpu]>=2.
|
|
34
|
-
"abstractruntime[gpu]>=0.
|
|
33
|
+
"abstractcore[all-gpu]>=2.25.0",
|
|
34
|
+
"abstractruntime[gpu]>=0.7.0",
|
|
35
35
|
}
|
|
36
36
|
assert "all-apple" not in extras
|
|
37
37
|
assert "all-gpu" not in extras
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""ReAct sends THE runtime history window when the host asks for it
|
|
2
|
+
(`_runtime.history_window_tokens`; runtime 0.7.0's entity visit sets 50k).
|
|
3
|
+
Unset, the whole transcript rides as before. The durable transcript is never
|
|
4
|
+
windowed."""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
from abstractagent.adapters.react_runtime import create_react_workflow
|
|
11
|
+
from abstractagent.logic.react import ReActLogic
|
|
12
|
+
from abstractcore.tools import ToolDefinition
|
|
13
|
+
from abstractruntime.core.models import Effect, EffectType, RunState, RunStatus
|
|
14
|
+
from abstractruntime.core.runtime import EffectOutcome, Runtime
|
|
15
|
+
from abstractruntime.storage.in_memory import InMemoryLedgerStore, InMemoryRunStore
|
|
16
|
+
from abstractruntime.turn_grounding import strip_turn_grounding
|
|
17
|
+
|
|
18
|
+
OLD = [m for i in range(6) for m in ({"role": "user", "content": f"old {i} " + "w " * 2000},
|
|
19
|
+
{"role": "assistant", "content": f"reply {i}"})]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _run(runtime_vars: Dict[str, Any]) -> tuple:
|
|
23
|
+
captured: List[Dict[str, Any]] = []
|
|
24
|
+
|
|
25
|
+
def llm(run: RunState, effect: Effect, dnn: Optional[str]) -> EffectOutcome:
|
|
26
|
+
captured.append(json.loads(json.dumps(effect.payload)))
|
|
27
|
+
return EffectOutcome.completed({"content": "done", "tool_calls": []})
|
|
28
|
+
|
|
29
|
+
rt = Runtime(run_store=InMemoryRunStore(), ledger_store=InMemoryLedgerStore(),
|
|
30
|
+
effect_handlers={EffectType.LLM_CALL: llm})
|
|
31
|
+
wf = create_react_workflow(logic=ReActLogic(tools=[ToolDefinition(name="t", description="t", parameters={})]),
|
|
32
|
+
workflow_id="react_window", provider="stub", model="stub", allowed_tools=["t"])
|
|
33
|
+
messages = [dict(m) for m in OLD] + [{"role": "user", "content": "now"}]
|
|
34
|
+
run_id = rt.start(workflow=wf, vars={"context": {"task": "now", "messages": messages}, "_runtime": dict(runtime_vars)})
|
|
35
|
+
state = rt.tick(workflow=wf, run_id=run_id, max_steps=40)
|
|
36
|
+
assert state.status == RunStatus.COMPLETED
|
|
37
|
+
return captured[0]["messages"], state
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_the_window_key_windows_the_request_and_records_it() -> None:
|
|
41
|
+
wire, state = _run({"history_window_tokens": 3000})
|
|
42
|
+
report = state.vars["_runtime"]["session_history"]
|
|
43
|
+
assert report["max_tokens"] == 3000 and report["dropped_messages"] > 0
|
|
44
|
+
assert len(wire) == report["replayed_messages"] < len(OLD) + 1
|
|
45
|
+
assert strip_turn_grounding(wire[0]["content"]).startswith("[#TRUNCATION: ")
|
|
46
|
+
assert strip_turn_grounding(wire[-1]["content"]) == "now"
|
|
47
|
+
stored = state.vars["context"]["messages"]
|
|
48
|
+
assert len(stored) >= len(OLD) + 1 and stored[0]["content"].startswith("old 0 ") # durable transcript whole
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_without_the_key_the_whole_transcript_rides() -> None:
|
|
52
|
+
wire, state = _run({})
|
|
53
|
+
assert "session_history" not in state.vars["_runtime"]
|
|
54
|
+
assert len(wire) == len(OLD) + 1 and wire[0]["content"].startswith("old 0 ")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_the_turn_in_progress_is_kept_whole_from_the_hosts_turn_start() -> None:
|
|
58
|
+
"""`_runtime.history_window_turn_start` marks where the turn in progress
|
|
59
|
+
began: from there on it is one turn, kept whole (and reported oversize),
|
|
60
|
+
however many user-role messages the loop added inside it."""
|
|
61
|
+
wire, state = _run({"history_window_tokens": 3000, "history_window_turn_start": 0})
|
|
62
|
+
report = state.vars["_runtime"]["session_history"]
|
|
63
|
+
assert report["dropped_messages"] == 0 and report["oversize_turn_kept"] is True
|
|
64
|
+
assert len(wire) == len(OLD) + 1 and wire[0]["content"].startswith("old 0 ")
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
abstractcore[tools]>=2.13.41
|
|
2
|
-
abstractruntime>=0.5.0
|
|
3
|
-
|
|
4
|
-
[apple]
|
|
5
|
-
abstractcore[all-apple]>=2.13.41
|
|
6
|
-
abstractruntime[apple]>=0.5.0
|
|
7
|
-
|
|
8
|
-
[dev]
|
|
9
|
-
pytest>=7.0
|
|
10
|
-
|
|
11
|
-
[gpu]
|
|
12
|
-
abstractcore[all-gpu]>=2.13.41
|
|
13
|
-
abstractruntime[gpu]>=0.5.0
|
|
14
|
-
|
|
15
|
-
[test]
|
|
16
|
-
pytest>=7.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/adapters/generation_params.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/adapters/native_loop_registry.py
RENAMED
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/adapters/read_orchestration.py
RENAMED
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.16 → abstractagent-0.3.18}/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
|
|
File without changes
|
{abstractagent-0.3.16 → abstractagent-0.3.18}/src/abstractagent/scripts/lmstudio_tool_eval.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.16 → abstractagent-0.3.18}/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.16 → abstractagent-0.3.18}/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.16 → abstractagent-0.3.18}/tests/test_delegate_session_route_composition.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_generation_params_media_policies.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.16 → abstractagent-0.3.18}/tests/test_react_election_fence_conformance.py
RENAMED
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_guidance_durable_transcript.py
RENAMED
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_loop_aborted_generation_retry.py
RENAMED
|
File without changes
|
{abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_loop_context_transcript_levels.py
RENAMED
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.16 → abstractagent-0.3.18}/tests/test_react_loop_side_effect_dedupe_comms.py
RENAMED
|
File without changes
|
{abstractagent-0.3.16 → abstractagent-0.3.18}/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.16 → abstractagent-0.3.18}/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.16 → abstractagent-0.3.18}/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
|