steerable-agent-runtime 0.6.7__tar.gz → 0.6.9__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.
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/PKG-INFO +1 -1
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/pyproject.toml +1 -1
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/compaction.py +48 -2
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/loop.py +20 -3
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime.egg-info/PKG-INFO +1 -1
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_antihallucination.py +37 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_compaction.py +82 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_loop.py +1 -1
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/README.md +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/setup.cfg +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/__init__.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/antihallucination.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/approval.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/approval_policy.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/ask_user.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/branch.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/cache_control.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/calibration.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/config.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/default.harness.json +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/default.harness.yaml +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/errors.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/gateway_catalog.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/handoff.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/harness.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/harness_spec.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/history.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/hooks.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/llm/__init__.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/llm/anthropic_native.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/llm/compat.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/llm/errors.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/llm/google_genai.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/llm/openai_compat.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/llm/openai_responses.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/llm/parts.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/llm/presets.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/llm/system_proxy.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/maintenance.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/mcp.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/mcp_server.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/model_catalog.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/model_info.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/model_resolve.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/observation_aging.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/orchestration.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/otel.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/plugins.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/pool.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/pricing.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/pseudo.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/recording.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/reminders.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/replay.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/resume.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/retry.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/sandboxed.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/skills.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/spill.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/storage/__init__.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/storage/in_memory.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/storage/sqlalchemy_store.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/storage/sqlite_store.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/storage/write_lease.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/subagent.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/todo.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/tokens.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/tool_schema.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/tool_search.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/tools.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/tracing.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/transport/__init__.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/transport/fastapi_sse.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/transport/stdio_jsonrpc.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/world_state.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime.egg-info/SOURCES.txt +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime.egg-info/dependency_links.txt +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime.egg-info/requires.txt +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime.egg-info/top_level.txt +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_approval.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_approval_policy.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_ask_user.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_branch.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_cache_control.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_cache_instrumentation.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_calibration.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_config.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_content_parts.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_error_taxonomy.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_fragment_bounds.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_gateway_catalog.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_golden.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_handoff.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_harness.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_harness_spec.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_history.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_history_persistence.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_hooks.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_in_memory_storage.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_llm_gemini_wire.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_llm_responses_wire.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_llm_stream_e2e.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_llm_wire_helpers.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_long_session.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_loop_cancellation.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_loop_replay.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_loop_sandbox_event.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_maintenance.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_mcp.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_mcp_server.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_model_catalog.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_model_equivalence.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_model_info.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_model_resolve.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_observation_aging.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_orchestration.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_otel.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_parallel_tools.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_plugins.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_provider_compat.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_provider_presets.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_pseudo.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_recording.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_reminders.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_replay_crosslang.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_resume.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_retry_hooks.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_safety_gate.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_sandboxed.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_skills.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_soft_timeout.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_spill.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_sqlite_storage.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_steer.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_storage_contract.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_stream_strip.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_subagent.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_system_proxy.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_todo.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_todo_gate.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_tokens.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_tool_exposure.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_tool_hygiene.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_tool_router.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_tool_schema.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_tool_timeout.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_trace_recorder.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_transport_jsonrpc.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_transport_sse.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_usage_attribution.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_world_state.py +0 -0
- {steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_write_lease.py +0 -0
|
@@ -22,7 +22,9 @@ Strategy (deterministic first, LLM optional):
|
|
|
22
22
|
message; otherwise drop the middle span behind a marker.
|
|
23
23
|
|
|
24
24
|
System messages and the first user message (the goal) are always kept; the
|
|
25
|
-
most recent ``keep_last_messages`` are never touched
|
|
25
|
+
most recent ``keep_last_messages`` are never touched, and the kept tail is
|
|
26
|
+
widened when that count would cut a tool-call group in half (an orphan
|
|
27
|
+
``tool`` message is a provider-level 400). Between compactions
|
|
26
28
|
the transcript is append-only, so provider prompt caches keep hitting; a
|
|
27
29
|
rewrite invalidates the cache once, then the prefix is stable again.
|
|
28
30
|
|
|
@@ -440,6 +442,50 @@ class CompactionHooks(NoopHooks):
|
|
|
440
442
|
for i, m in enumerate(transcript)
|
|
441
443
|
]
|
|
442
444
|
|
|
445
|
+
@staticmethod
|
|
446
|
+
def _orphan_tool_ids(tail: Sequence[LLMMessage]) -> set[str]:
|
|
447
|
+
"""``tool_call_id``s answered inside ``tail`` but issued before it."""
|
|
448
|
+
issued: set[str] = set()
|
|
449
|
+
orphans: set[str] = set()
|
|
450
|
+
for m in tail:
|
|
451
|
+
for call in m.tool_calls or ():
|
|
452
|
+
issued.add(call.id)
|
|
453
|
+
if (
|
|
454
|
+
m.role == "tool"
|
|
455
|
+
and m.tool_call_id is not None
|
|
456
|
+
and m.tool_call_id not in issued
|
|
457
|
+
):
|
|
458
|
+
orphans.add(m.tool_call_id)
|
|
459
|
+
return orphans
|
|
460
|
+
|
|
461
|
+
def _tail_start(self, transcript: list[LLMMessage], head_end: int) -> int:
|
|
462
|
+
"""Where the untouched tail begins: ``keep_last_messages`` back, then
|
|
463
|
+
widened until the tail answers no tool call it does not also issue.
|
|
464
|
+
|
|
465
|
+
OpenAI-compatible providers reject a ``tool`` message whose
|
|
466
|
+
``tool_call_id`` was never issued by a preceding ``assistant``
|
|
467
|
+
message (DeepSeek answers HTTP 400 ``invalid_request``). A count-only
|
|
468
|
+
cut can land inside a tool-call group, summarizing the issuing
|
|
469
|
+
assistant away while its results stay in the tail. Widening only ever
|
|
470
|
+
keeps more, and is bounded by one round's tool calls — including the
|
|
471
|
+
``user`` image messages the read path interleaves between results.
|
|
472
|
+
|
|
473
|
+
Only orphans this cut created are worth widening for. A transcript
|
|
474
|
+
that arrives already missing an issuer (a host that resumed from a
|
|
475
|
+
truncated log) would otherwise widen to ``head_end`` and disable
|
|
476
|
+
summarization for the rest of the run, trading a provider 400 for a
|
|
477
|
+
context overflow.
|
|
478
|
+
"""
|
|
479
|
+
tail_start = max(head_end, len(transcript) - self._keep_last)
|
|
480
|
+
issuable = {
|
|
481
|
+
call.id for m in transcript[head_end:] for call in m.tool_calls or ()
|
|
482
|
+
}
|
|
483
|
+
while tail_start > head_end and (
|
|
484
|
+
self._orphan_tool_ids(transcript[tail_start:]) & issuable
|
|
485
|
+
):
|
|
486
|
+
tail_start -= 1
|
|
487
|
+
return tail_start
|
|
488
|
+
|
|
443
489
|
async def _summarize_middle(self, transcript: list[LLMMessage]) -> list[LLMMessage]:
|
|
444
490
|
# Keep every system message plus the first user message (the goal) as
|
|
445
491
|
# the head; keep the recent tail untouched; summarize the middle.
|
|
@@ -451,7 +497,7 @@ class CompactionHooks(NoopHooks):
|
|
|
451
497
|
head_end = i + 1
|
|
452
498
|
else:
|
|
453
499
|
break
|
|
454
|
-
tail_start =
|
|
500
|
+
tail_start = self._tail_start(transcript, head_end)
|
|
455
501
|
middle = transcript[head_end:tail_start]
|
|
456
502
|
if not middle:
|
|
457
503
|
return transcript
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/loop.py
RENAMED
|
@@ -877,6 +877,13 @@ class CoreLoop:
|
|
|
877
877
|
else None
|
|
878
878
|
)
|
|
879
879
|
wrap_up = False
|
|
880
|
+
# Terminal that triggered the current wrap-up when a budget wall
|
|
881
|
+
# (maxRounds) offered narration. The wrap-up summary is an interim
|
|
882
|
+
# report, not task completion: the no-tools terminal below restores
|
|
883
|
+
# this decision so the host's auto-continue sees budget_exhausted and
|
|
884
|
+
# resumes with a fresh budget instead of mistaking the summary for a
|
|
885
|
+
# finished task.
|
|
886
|
+
wrap_up_terminal: CompletionDecision | None = None
|
|
880
887
|
wrap_up_tool_rounds_used = 0
|
|
881
888
|
idle_cut_count = 0
|
|
882
889
|
|
|
@@ -1051,7 +1058,10 @@ class CoreLoop:
|
|
|
1051
1058
|
)
|
|
1052
1059
|
yield LoopEvent(
|
|
1053
1060
|
"budget_exhausted",
|
|
1054
|
-
|
|
1061
|
+
# "budget" not "kind": the sidecar wraps loop events as
|
|
1062
|
+
# {"kind": <event kind>, **data} — a data["kind"] would
|
|
1063
|
+
# clobber the envelope kind hosts dispatch on.
|
|
1064
|
+
{"budget": "rounds", "rounds": self._config.max_rounds},
|
|
1055
1065
|
)
|
|
1056
1066
|
narrate = await self._offer_narration(
|
|
1057
1067
|
"", round_index, had_tool_calls=True, ctx=ctx,
|
|
@@ -1060,6 +1070,7 @@ class CoreLoop:
|
|
|
1060
1070
|
if narrate is not None:
|
|
1061
1071
|
prompt, reason = narrate
|
|
1062
1072
|
completion_redos += 1
|
|
1073
|
+
wrap_up_terminal = decision
|
|
1063
1074
|
mark_wrap_up()
|
|
1064
1075
|
manager.append_fragment(NarrationRequest(prompt))
|
|
1065
1076
|
yield LoopEvent(
|
|
@@ -1362,7 +1373,7 @@ class CoreLoop:
|
|
|
1362
1373
|
if exhausted:
|
|
1363
1374
|
yield LoopEvent(
|
|
1364
1375
|
"budget_exhausted",
|
|
1365
|
-
{"
|
|
1376
|
+
{"budget": "tokens", "used": budget_state.tokens_used},
|
|
1366
1377
|
)
|
|
1367
1378
|
decision = CompletionDecision(
|
|
1368
1379
|
status="budget_exhausted", reason="token budget exceeded"
|
|
@@ -1586,7 +1597,13 @@ class CoreLoop:
|
|
|
1586
1597
|
|
|
1587
1598
|
# ── decide: no tool calls → terminal (unless a hook vetoes) ──
|
|
1588
1599
|
if not tool_calls:
|
|
1589
|
-
if
|
|
1600
|
+
if wrap_up_terminal is not None:
|
|
1601
|
+
# Budget-wall wrap-up: the narration summary is an interim
|
|
1602
|
+
# report, not a finished task. Restore the wall's terminal
|
|
1603
|
+
# (budget_exhausted) so the host auto-continues with a
|
|
1604
|
+
# fresh budget instead of stopping on a half-done task.
|
|
1605
|
+
decision = wrap_up_terminal
|
|
1606
|
+
elif content.strip():
|
|
1590
1607
|
decision = CompletionDecision(
|
|
1591
1608
|
status="completed",
|
|
1592
1609
|
reason="assistant produced final response with no pending tools",
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_antihallucination.py
RENAMED
|
@@ -17,6 +17,7 @@ from steerable_agent_runtime import (
|
|
|
17
17
|
AntiHallucinationConfig,
|
|
18
18
|
AntiHallucinationHooks,
|
|
19
19
|
CoreLoop,
|
|
20
|
+
LoopConfig,
|
|
20
21
|
LoopEvent,
|
|
21
22
|
RouterToolExecutor,
|
|
22
23
|
ToolRouter,
|
|
@@ -521,6 +522,42 @@ async def test_narration_round_on_empty_terminal() -> None:
|
|
|
521
522
|
)
|
|
522
523
|
|
|
523
524
|
|
|
525
|
+
@pytest.mark.asyncio
|
|
526
|
+
async def test_narration_at_max_rounds_wall_stays_budget_exhausted() -> None:
|
|
527
|
+
"""The maxRounds wall interrupts work; the narration summary it triggers
|
|
528
|
+
is an interim report, not task completion. The terminal must keep
|
|
529
|
+
budget_exhausted so the host's auto-continue resumes with a fresh budget
|
|
530
|
+
(a completed status would silently stop a half-done task)."""
|
|
531
|
+
provider = make_provider(
|
|
532
|
+
[
|
|
533
|
+
{"content": "", "tool_calls": [tc("get_data", call_id="c1")]},
|
|
534
|
+
{"content": "", "tool_calls": [tc("get_data", {"source": "a"}, "c2")]},
|
|
535
|
+
{"content": "", "tool_calls": [tc("get_data", {"source": "b"}, "c3")]},
|
|
536
|
+
# narration wrap-up round (tools withheld): interim summary
|
|
537
|
+
{"content": "阶段性总结:已取数 3 次。"},
|
|
538
|
+
]
|
|
539
|
+
)
|
|
540
|
+
hooks = AntiHallucinationHooks(provider, AntiHallucinationConfig())
|
|
541
|
+
loop = CoreLoop(
|
|
542
|
+
provider,
|
|
543
|
+
RouterToolExecutor(make_router_with_tool()),
|
|
544
|
+
LoopConfig(max_rounds=3),
|
|
545
|
+
hooks=hooks,
|
|
546
|
+
)
|
|
547
|
+
events = await collect(loop.run([LLMMessage.text_of("user", "取数")], tools=TOOLS))
|
|
548
|
+
|
|
549
|
+
final = final_completion(events)
|
|
550
|
+
assert final["status"] == "budget_exhausted"
|
|
551
|
+
assert "maxRounds" in final["reason"]
|
|
552
|
+
# The interim summary still rides the completion and the transcript.
|
|
553
|
+
assert final["textLength"] == len("阶段性总结:已取数 3 次。")
|
|
554
|
+
narrations = [
|
|
555
|
+
e for e in events
|
|
556
|
+
if e.kind == "hook_action" and e.data.get("action") == "narrate"
|
|
557
|
+
]
|
|
558
|
+
assert len(narrations) == 1
|
|
559
|
+
|
|
560
|
+
|
|
524
561
|
@pytest.mark.asyncio
|
|
525
562
|
async def test_narration_second_chance_on_empty_wrap_up() -> None:
|
|
526
563
|
"""Real-data replay finding: the wrap-up round itself can come back empty
|
|
@@ -437,6 +437,88 @@ async def test_compaction_preserves_system_and_first_user_message() -> None:
|
|
|
437
437
|
assert call[1].role == "user" and call[1].content_text == "the original goal"
|
|
438
438
|
|
|
439
439
|
|
|
440
|
+
def tool_result(call_id: str) -> LLMMessage:
|
|
441
|
+
return LLMMessage.text_of("tool", "ok", name="emit", tool_call_id=call_id)
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
@pytest.mark.asyncio
|
|
445
|
+
async def test_summarize_middle_widens_the_tail_off_an_orphan_tool_result() -> None:
|
|
446
|
+
# A count-only tail cut can land inside a tool-call group: the issuing
|
|
447
|
+
# assistant goes into the summarized middle while its results stay in the
|
|
448
|
+
# tail, and the request opens on a `tool` message nothing answered.
|
|
449
|
+
# DeepSeek rejects that with HTTP 400 invalid_request.
|
|
450
|
+
calls = [
|
|
451
|
+
ToolCall(id="call_a", name="emit", arguments={}),
|
|
452
|
+
ToolCall(id="call_b", name="emit", arguments={}),
|
|
453
|
+
]
|
|
454
|
+
transcript = [
|
|
455
|
+
LLMMessage.text_of("system", "you are helpful"),
|
|
456
|
+
LLMMessage.text_of("user", "make a deck"),
|
|
457
|
+
LLMMessage.text_of("assistant", "reading the skill files"),
|
|
458
|
+
LLMMessage.text_of("assistant", "", tool_calls=calls),
|
|
459
|
+
tool_result("call_a"),
|
|
460
|
+
# the read path interleaves image messages between tool results
|
|
461
|
+
LLMMessage.text_of("user", "slide.png:"),
|
|
462
|
+
tool_result("call_b"),
|
|
463
|
+
LLMMessage.text_of("assistant", "wrapping up"),
|
|
464
|
+
]
|
|
465
|
+
hooks = CompactionHooks(max_context_tokens=1_000, keep_last_messages=3)
|
|
466
|
+
|
|
467
|
+
out = await hooks._summarize_middle(transcript)
|
|
468
|
+
|
|
469
|
+
assert CompactionHooks._orphan_tool_ids(out) == set()
|
|
470
|
+
assert any("[context compacted" in m.content_text for m in out)
|
|
471
|
+
# the group survived whole, in order, with its interleaved image message
|
|
472
|
+
assert [m.role for m in out[-5:]] == [
|
|
473
|
+
"assistant",
|
|
474
|
+
"tool",
|
|
475
|
+
"user",
|
|
476
|
+
"tool",
|
|
477
|
+
"assistant",
|
|
478
|
+
]
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
@pytest.mark.asyncio
|
|
482
|
+
async def test_summarize_middle_still_compacts_an_already_orphaned_transcript() -> None:
|
|
483
|
+
# The issuer is missing from the input (a host resuming from a truncated
|
|
484
|
+
# log), so widening could never repair the tail. Compaction proceeds
|
|
485
|
+
# rather than widening to the head and going silent for the rest of the
|
|
486
|
+
# run — the alternative to the provider 400 is a context overflow.
|
|
487
|
+
transcript = [
|
|
488
|
+
LLMMessage.text_of("user", "make a deck"),
|
|
489
|
+
LLMMessage.text_of("assistant", "first"),
|
|
490
|
+
tool_result("call_gone_a"),
|
|
491
|
+
tool_result("call_gone_b"),
|
|
492
|
+
LLMMessage.text_of("assistant", "wrapping up"),
|
|
493
|
+
]
|
|
494
|
+
hooks = CompactionHooks(max_context_tokens=1_000, keep_last_messages=2)
|
|
495
|
+
|
|
496
|
+
out = await hooks._summarize_middle(transcript)
|
|
497
|
+
|
|
498
|
+
assert len(out) < len(transcript)
|
|
499
|
+
assert any("[context compacted" in m.content_text for m in out)
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
@pytest.mark.asyncio
|
|
503
|
+
async def test_summarize_middle_keeps_an_aligned_tail_at_keep_last() -> None:
|
|
504
|
+
# No tool-call group is cut, so the tail stays exactly keep_last long —
|
|
505
|
+
# widening must not cost compaction on a clean boundary.
|
|
506
|
+
transcript = [
|
|
507
|
+
LLMMessage.text_of("system", "you are helpful"),
|
|
508
|
+
LLMMessage.text_of("user", "make a deck"),
|
|
509
|
+
LLMMessage.text_of("assistant", "first"),
|
|
510
|
+
LLMMessage.text_of("assistant", "second"),
|
|
511
|
+
LLMMessage.text_of("user", "keep going"),
|
|
512
|
+
LLMMessage.text_of("assistant", "third"),
|
|
513
|
+
]
|
|
514
|
+
hooks = CompactionHooks(max_context_tokens=1_000, keep_last_messages=3)
|
|
515
|
+
|
|
516
|
+
out = await hooks._summarize_middle(transcript)
|
|
517
|
+
|
|
518
|
+
assert [m.content_text for m in out[-3:]] == ["second", "keep going", "third"]
|
|
519
|
+
assert len(out) == 6 # system + goal + summary + 3 kept
|
|
520
|
+
|
|
521
|
+
|
|
440
522
|
@pytest.mark.asyncio
|
|
441
523
|
async def test_micro_compact_trigger_schedule() -> None:
|
|
442
524
|
"""Periodic prune fires on interval multiples (never round 0), and an
|
|
@@ -410,7 +410,7 @@ async def test_token_budget_exhausted() -> None:
|
|
|
410
410
|
events = await collect(loop.run([LLMMessage.text_of("user", "hi")]))
|
|
411
411
|
decision = final_completion(events)
|
|
412
412
|
assert decision["status"] == "budget_exhausted"
|
|
413
|
-
assert any(e.kind == "budget_exhausted" and e.data["
|
|
413
|
+
assert any(e.kind == "budget_exhausted" and e.data["budget"] == "tokens" for e in events)
|
|
414
414
|
|
|
415
415
|
|
|
416
416
|
@pytest.mark.asyncio
|
|
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
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/hooks.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
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/mcp.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/otel.py
RENAMED
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/pool.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/retry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/spill.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/todo.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/tools.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
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_approval_policy.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_cache_instrumentation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_error_taxonomy.py
RENAMED
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_fragment_bounds.py
RENAMED
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_gateway_catalog.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_history_persistence.py
RENAMED
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_in_memory_storage.py
RENAMED
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_llm_gemini_wire.py
RENAMED
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_llm_responses_wire.py
RENAMED
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_llm_stream_e2e.py
RENAMED
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_llm_wire_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_loop_cancellation.py
RENAMED
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_loop_sandbox_event.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_model_equivalence.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_observation_aging.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_parallel_tools.py
RENAMED
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_provider_compat.py
RENAMED
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_provider_presets.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_replay_crosslang.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
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_sqlite_storage.py
RENAMED
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_storage_contract.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
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_trace_recorder.py
RENAMED
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_transport_jsonrpc.py
RENAMED
|
File without changes
|
|
File without changes
|
{steerable_agent_runtime-0.6.7 → steerable_agent_runtime-0.6.9}/tests/test_usage_attribution.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|