steerable-agent-runtime 0.6.8__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.
Files changed (152) hide show
  1. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/PKG-INFO +1 -1
  2. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/pyproject.toml +1 -1
  3. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/loop.py +20 -3
  4. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime.egg-info/PKG-INFO +1 -1
  5. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_antihallucination.py +37 -0
  6. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_loop.py +1 -1
  7. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/README.md +0 -0
  8. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/setup.cfg +0 -0
  9. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/__init__.py +0 -0
  10. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/antihallucination.py +0 -0
  11. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/approval.py +0 -0
  12. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/approval_policy.py +0 -0
  13. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/ask_user.py +0 -0
  14. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/branch.py +0 -0
  15. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/cache_control.py +0 -0
  16. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/calibration.py +0 -0
  17. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/compaction.py +0 -0
  18. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/config.py +0 -0
  19. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/default.harness.json +0 -0
  20. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/default.harness.yaml +0 -0
  21. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/errors.py +0 -0
  22. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/gateway_catalog.py +0 -0
  23. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/handoff.py +0 -0
  24. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/harness.py +0 -0
  25. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/harness_spec.py +0 -0
  26. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/history.py +0 -0
  27. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/hooks.py +0 -0
  28. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/llm/__init__.py +0 -0
  29. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/llm/anthropic_native.py +0 -0
  30. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/llm/compat.py +0 -0
  31. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/llm/errors.py +0 -0
  32. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/llm/google_genai.py +0 -0
  33. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/llm/openai_compat.py +0 -0
  34. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/llm/openai_responses.py +0 -0
  35. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/llm/parts.py +0 -0
  36. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/llm/presets.py +0 -0
  37. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/llm/system_proxy.py +0 -0
  38. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/maintenance.py +0 -0
  39. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/mcp.py +0 -0
  40. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/mcp_server.py +0 -0
  41. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/model_catalog.py +0 -0
  42. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/model_info.py +0 -0
  43. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/model_resolve.py +0 -0
  44. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/observation_aging.py +0 -0
  45. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/orchestration.py +0 -0
  46. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/otel.py +0 -0
  47. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/plugins.py +0 -0
  48. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/pool.py +0 -0
  49. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/pricing.py +0 -0
  50. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/pseudo.py +0 -0
  51. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/recording.py +0 -0
  52. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/reminders.py +0 -0
  53. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/replay.py +0 -0
  54. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/resume.py +0 -0
  55. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/retry.py +0 -0
  56. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/sandboxed.py +0 -0
  57. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/skills.py +0 -0
  58. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/spill.py +0 -0
  59. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/storage/__init__.py +0 -0
  60. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/storage/in_memory.py +0 -0
  61. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/storage/sqlalchemy_store.py +0 -0
  62. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/storage/sqlite_store.py +0 -0
  63. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/storage/write_lease.py +0 -0
  64. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/subagent.py +0 -0
  65. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/todo.py +0 -0
  66. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/tokens.py +0 -0
  67. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/tool_schema.py +0 -0
  68. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/tool_search.py +0 -0
  69. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/tools.py +0 -0
  70. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/tracing.py +0 -0
  71. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/transport/__init__.py +0 -0
  72. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/transport/fastapi_sse.py +0 -0
  73. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/transport/stdio_jsonrpc.py +0 -0
  74. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime/world_state.py +0 -0
  75. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime.egg-info/SOURCES.txt +0 -0
  76. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime.egg-info/dependency_links.txt +0 -0
  77. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime.egg-info/requires.txt +0 -0
  78. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/src/steerable_agent_runtime.egg-info/top_level.txt +0 -0
  79. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_approval.py +0 -0
  80. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_approval_policy.py +0 -0
  81. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_ask_user.py +0 -0
  82. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_branch.py +0 -0
  83. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_cache_control.py +0 -0
  84. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_cache_instrumentation.py +0 -0
  85. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_calibration.py +0 -0
  86. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_compaction.py +0 -0
  87. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_config.py +0 -0
  88. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_content_parts.py +0 -0
  89. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_error_taxonomy.py +0 -0
  90. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_fragment_bounds.py +0 -0
  91. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_gateway_catalog.py +0 -0
  92. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_golden.py +0 -0
  93. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_handoff.py +0 -0
  94. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_harness.py +0 -0
  95. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_harness_spec.py +0 -0
  96. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_history.py +0 -0
  97. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_history_persistence.py +0 -0
  98. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_hooks.py +0 -0
  99. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_in_memory_storage.py +0 -0
  100. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_llm_gemini_wire.py +0 -0
  101. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_llm_responses_wire.py +0 -0
  102. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_llm_stream_e2e.py +0 -0
  103. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_llm_wire_helpers.py +0 -0
  104. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_long_session.py +0 -0
  105. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_loop_cancellation.py +0 -0
  106. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_loop_replay.py +0 -0
  107. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_loop_sandbox_event.py +0 -0
  108. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_maintenance.py +0 -0
  109. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_mcp.py +0 -0
  110. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_mcp_server.py +0 -0
  111. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_model_catalog.py +0 -0
  112. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_model_equivalence.py +0 -0
  113. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_model_info.py +0 -0
  114. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_model_resolve.py +0 -0
  115. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_observation_aging.py +0 -0
  116. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_orchestration.py +0 -0
  117. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_otel.py +0 -0
  118. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_parallel_tools.py +0 -0
  119. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_plugins.py +0 -0
  120. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_provider_compat.py +0 -0
  121. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_provider_presets.py +0 -0
  122. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_pseudo.py +0 -0
  123. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_recording.py +0 -0
  124. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_reminders.py +0 -0
  125. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_replay_crosslang.py +0 -0
  126. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_resume.py +0 -0
  127. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_retry_hooks.py +0 -0
  128. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_safety_gate.py +0 -0
  129. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_sandboxed.py +0 -0
  130. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_skills.py +0 -0
  131. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_soft_timeout.py +0 -0
  132. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_spill.py +0 -0
  133. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_sqlite_storage.py +0 -0
  134. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_steer.py +0 -0
  135. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_storage_contract.py +0 -0
  136. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_stream_strip.py +0 -0
  137. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_subagent.py +0 -0
  138. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_system_proxy.py +0 -0
  139. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_todo.py +0 -0
  140. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_todo_gate.py +0 -0
  141. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_tokens.py +0 -0
  142. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_tool_exposure.py +0 -0
  143. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_tool_hygiene.py +0 -0
  144. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_tool_router.py +0 -0
  145. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_tool_schema.py +0 -0
  146. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_tool_timeout.py +0 -0
  147. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_trace_recorder.py +0 -0
  148. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_transport_jsonrpc.py +0 -0
  149. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_transport_sse.py +0 -0
  150. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_usage_attribution.py +0 -0
  151. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_world_state.py +0 -0
  152. {steerable_agent_runtime-0.6.8 → steerable_agent_runtime-0.6.9}/tests/test_write_lease.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: steerable-agent-runtime
3
- Version: 0.6.8
3
+ Version: 0.6.9
4
4
  Summary: Steerable agent runtime: LLM, tool, storage, and transport adapters.
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "steerable-agent-runtime"
3
- version = "0.6.8"
3
+ version = "0.6.9"
4
4
  description = "Steerable agent runtime: LLM, tool, storage, and transport adapters."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -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
- {"kind": "rounds", "rounds": self._config.max_rounds},
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
- {"kind": "tokens", "used": budget_state.tokens_used},
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 content.strip():
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",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: steerable-agent-runtime
3
- Version: 0.6.8
3
+ Version: 0.6.9
4
4
  Summary: Steerable agent runtime: LLM, tool, storage, and transport adapters.
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -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
@@ -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["kind"] == "tokens" for e in events)
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