steerable-agent-runtime 0.6.3__tar.gz → 0.6.5__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.3 → steerable_agent_runtime-0.6.5}/PKG-INFO +1 -1
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/pyproject.toml +1 -1
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/__init__.py +12 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/ask_user.py +17 -7
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/compaction.py +71 -4
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/harness.py +7 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/llm/__init__.py +4 -0
- steerable_agent_runtime-0.6.5/src/steerable_agent_runtime/llm/google_genai.py +444 -0
- steerable_agent_runtime-0.6.5/src/steerable_agent_runtime/llm/openai_responses.py +518 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/reminders.py +33 -0
- steerable_agent_runtime-0.6.5/src/steerable_agent_runtime/todo.py +213 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime.egg-info/PKG-INFO +1 -1
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime.egg-info/SOURCES.txt +7 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_ask_user.py +29 -6
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_compaction.py +289 -0
- steerable_agent_runtime-0.6.5/tests/test_llm_gemini_wire.py +256 -0
- steerable_agent_runtime-0.6.5/tests/test_llm_responses_wire.py +368 -0
- steerable_agent_runtime-0.6.5/tests/test_llm_stream_e2e.py +233 -0
- steerable_agent_runtime-0.6.5/tests/test_todo.py +156 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/README.md +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/setup.cfg +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/antihallucination.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/approval.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/approval_policy.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/branch.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/cache_control.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/calibration.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/config.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/default.harness.json +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/default.harness.yaml +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/errors.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/handoff.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/harness_spec.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/history.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/hooks.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/llm/anthropic_native.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/llm/compat.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/llm/errors.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/llm/openai_compat.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/llm/parts.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/llm/presets.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/llm/system_proxy.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/loop.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/maintenance.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/mcp.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/mcp_server.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/model_catalog.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/model_info.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/model_resolve.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/observation_aging.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/orchestration.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/otel.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/plugins.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/pool.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/pricing.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/pseudo.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/recording.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/replay.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/resume.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/retry.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/sandboxed.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/skills.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/spill.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/storage/__init__.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/storage/in_memory.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/storage/sqlalchemy_store.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/storage/sqlite_store.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/storage/write_lease.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/subagent.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/tokens.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/tool_schema.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/tool_search.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/tools.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/tracing.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/transport/__init__.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/transport/fastapi_sse.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/transport/stdio_jsonrpc.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime/world_state.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime.egg-info/dependency_links.txt +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime.egg-info/requires.txt +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/src/steerable_agent_runtime.egg-info/top_level.txt +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_antihallucination.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_approval.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_approval_policy.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_branch.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_cache_control.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_cache_instrumentation.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_calibration.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_config.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_content_parts.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_error_taxonomy.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_fragment_bounds.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_golden.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_handoff.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_harness.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_harness_spec.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_history.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_history_persistence.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_hooks.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_in_memory_storage.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_llm_wire_helpers.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_long_session.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_loop.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_loop_cancellation.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_loop_replay.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_loop_sandbox_event.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_maintenance.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_mcp.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_mcp_server.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_model_catalog.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_model_equivalence.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_model_info.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_model_resolve.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_observation_aging.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_orchestration.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_otel.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_parallel_tools.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_plugins.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_provider_compat.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_provider_presets.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_pseudo.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_recording.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_reminders.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_replay_crosslang.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_resume.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_retry_hooks.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_safety_gate.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_sandboxed.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_skills.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_soft_timeout.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_spill.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_sqlite_storage.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_steer.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_storage_contract.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_stream_strip.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_subagent.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_system_proxy.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_tokens.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_tool_exposure.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_tool_hygiene.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_tool_router.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_tool_schema.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_tool_timeout.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_trace_recorder.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_transport_jsonrpc.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_transport_sse.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_usage_attribution.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_world_state.py +0 -0
- {steerable_agent_runtime-0.6.3 → steerable_agent_runtime-0.6.5}/tests/test_write_lease.py +0 -0
|
@@ -35,6 +35,13 @@ from .ask_user import (
|
|
|
35
35
|
AskUserHandler,
|
|
36
36
|
make_ask_user_tool,
|
|
37
37
|
)
|
|
38
|
+
from .todo import (
|
|
39
|
+
TODO_SCHEMA,
|
|
40
|
+
TODO_TOOL_NAME,
|
|
41
|
+
TodoStore,
|
|
42
|
+
make_todo_write_tool,
|
|
43
|
+
todo_write_tool_descriptor,
|
|
44
|
+
)
|
|
38
45
|
from .config import (
|
|
39
46
|
DEFAULT_CONFIG_PATH,
|
|
40
47
|
ConfigError,
|
|
@@ -262,6 +269,8 @@ __all__ = [
|
|
|
262
269
|
"TOOL_SEARCH_NAME",
|
|
263
270
|
"ASK_USER_SCHEMA",
|
|
264
271
|
"ASK_USER_TOOL_NAME",
|
|
272
|
+
"TODO_SCHEMA",
|
|
273
|
+
"TODO_TOOL_NAME",
|
|
265
274
|
"STEERABLE_TOOLS_ENTRY_POINT_GROUP",
|
|
266
275
|
"AgentPool",
|
|
267
276
|
"AntiHallucinationConfig",
|
|
@@ -382,6 +391,7 @@ __all__ = [
|
|
|
382
391
|
"ToolExecutor",
|
|
383
392
|
"ToolExposure",
|
|
384
393
|
"ToolRouter",
|
|
394
|
+
"TodoStore",
|
|
385
395
|
"TraceRecorder",
|
|
386
396
|
"TranscriptAppend",
|
|
387
397
|
"TransportAdapter",
|
|
@@ -423,6 +433,7 @@ __all__ = [
|
|
|
423
433
|
"load_recorded_requests",
|
|
424
434
|
"load_transcript",
|
|
425
435
|
"make_ask_user_tool",
|
|
436
|
+
"make_todo_write_tool",
|
|
426
437
|
"matches_conditions",
|
|
427
438
|
"mcp_invoker",
|
|
428
439
|
"merge_patch",
|
|
@@ -456,6 +467,7 @@ __all__ = [
|
|
|
456
467
|
"system_blocks_with_cache",
|
|
457
468
|
"text_parts",
|
|
458
469
|
"to_otlp_json",
|
|
470
|
+
"todo_write_tool_descriptor",
|
|
459
471
|
"tool",
|
|
460
472
|
"tool_search_descriptor",
|
|
461
473
|
"upgrade_entry_dict",
|
|
@@ -71,9 +71,16 @@ ASK_USER_SCHEMA: dict[str, Any] = {
|
|
|
71
71
|
),
|
|
72
72
|
},
|
|
73
73
|
"placeholder": {"type": "string"},
|
|
74
|
-
"multiSelect": {
|
|
74
|
+
"multiSelect": {
|
|
75
|
+
"type": "boolean",
|
|
76
|
+
"description": (
|
|
77
|
+
"Whether the user may pick several options. "
|
|
78
|
+
"Required (CC parity): commit to single vs multi "
|
|
79
|
+
"explicitly — pass false for a single-select."
|
|
80
|
+
),
|
|
81
|
+
},
|
|
75
82
|
},
|
|
76
|
-
"required": ["id", "text"],
|
|
83
|
+
"required": ["id", "text", "multiSelect"],
|
|
77
84
|
},
|
|
78
85
|
},
|
|
79
86
|
},
|
|
@@ -170,12 +177,15 @@ def _normalize_questions(questions: list[dict[str, Any]]) -> list[dict[str, Any]
|
|
|
170
177
|
f"ask_user: questions[{index}].header is {len(header)} chars; "
|
|
171
178
|
f"the chip label must be <= {_MAX_HEADER_LEN}."
|
|
172
179
|
)
|
|
173
|
-
# multiSelect: CC requires the model to commit to single vs multi
|
|
174
|
-
#
|
|
175
|
-
#
|
|
180
|
+
# multiSelect: CC requires the model to commit to single vs multi —
|
|
181
|
+
# the field is required, so an omission is a model error to fix, not
|
|
182
|
+
# a default to assume.
|
|
176
183
|
if "multiSelect" not in q or q["multiSelect"] is None:
|
|
177
|
-
|
|
178
|
-
|
|
184
|
+
raise ToolDispatchError(
|
|
185
|
+
f"ask_user: questions[{index}].multiSelect is required "
|
|
186
|
+
"(CC parity) — pass false for a single-select question."
|
|
187
|
+
)
|
|
188
|
+
if not isinstance(q["multiSelect"], bool):
|
|
179
189
|
raise ToolDispatchError(
|
|
180
190
|
f"ask_user: questions[{index}].multiSelect must be a boolean, "
|
|
181
191
|
f"got {type(q['multiSelect']).__name__}."
|
|
@@ -57,6 +57,14 @@ Two safety rails share the machinery:
|
|
|
57
57
|
the turn still fails loud (bounded retries, then a named error) instead
|
|
58
58
|
of spinning. A round that lands under threshold — or any successful
|
|
59
59
|
compaction — resets the count.
|
|
60
|
+
- A **rapid-refill breaker** (CC parity): a compaction that lands under
|
|
61
|
+
threshold but refills within ``rapid_refill_window_rounds`` rounds of the
|
|
62
|
+
previous one counts as a refill; ``max_rapid_refills`` consecutive
|
|
63
|
+
refills open the same circuit — the transcript is churning faster than
|
|
64
|
+
compaction can help, so further rewrites only kill the prompt cache.
|
|
65
|
+
The tripping round appends a ``CompactionThrashingReminder`` (model- and
|
|
66
|
+
UI-visible) with an actionable converge notice. ``circuit_reason``
|
|
67
|
+
records which breaker tripped.
|
|
60
68
|
"""
|
|
61
69
|
|
|
62
70
|
from __future__ import annotations
|
|
@@ -64,9 +72,10 @@ from __future__ import annotations
|
|
|
64
72
|
from collections.abc import Sequence
|
|
65
73
|
from typing import Any
|
|
66
74
|
|
|
67
|
-
from .hooks import NoopHooks, PreStepAction, RetryAction, RewriteRequest
|
|
75
|
+
from .hooks import NoopHooks, PreStepAction, RetryAction, RewriteRequest, TranscriptAppend
|
|
68
76
|
from .llm import LLMMessage, LLMProvider
|
|
69
77
|
from .llm.errors import classify_error
|
|
78
|
+
from .reminders import CompactionThrashingReminder
|
|
70
79
|
from .tokens import estimate_tokens
|
|
71
80
|
|
|
72
81
|
_SUMMARY_MARKER = "[context compacted: earlier conversation summarized]"
|
|
@@ -108,6 +117,8 @@ class CompactionHooks(NoopHooks):
|
|
|
108
117
|
recompact_margin_ratio: float = 0.1,
|
|
109
118
|
fold_excerpt_chars: int = _FOLD_EXCERPT_CHARS,
|
|
110
119
|
micro_compact_interval_rounds: int = 0,
|
|
120
|
+
rapid_refill_window_rounds: int = 3,
|
|
121
|
+
max_rapid_refills: int = 3,
|
|
111
122
|
) -> None:
|
|
112
123
|
if not 0 < threshold_ratio <= 1:
|
|
113
124
|
raise ValueError("threshold_ratio must be in (0, 1]")
|
|
@@ -115,6 +126,10 @@ class CompactionHooks(NoopHooks):
|
|
|
115
126
|
raise ValueError("recompact_margin_ratio must be >= 0")
|
|
116
127
|
if not 0 <= micro_compact_interval_rounds:
|
|
117
128
|
raise ValueError("micro_compact_interval_rounds must be >= 0")
|
|
129
|
+
if not 1 <= rapid_refill_window_rounds:
|
|
130
|
+
raise ValueError("rapid_refill_window_rounds must be >= 1")
|
|
131
|
+
if not 1 <= max_rapid_refills:
|
|
132
|
+
raise ValueError("max_rapid_refills must be >= 1")
|
|
118
133
|
self._max_tokens = max_context_tokens
|
|
119
134
|
self._threshold = threshold_ratio
|
|
120
135
|
self._keep_last = keep_last_messages
|
|
@@ -155,9 +170,22 @@ class CompactionHooks(NoopHooks):
|
|
|
155
170
|
#: parity). The overflow path is bounded separately and unaffected.
|
|
156
171
|
self.max_consecutive_failures = 3
|
|
157
172
|
self._consecutive_failures = 0
|
|
173
|
+
#: Rapid-refill breaker (CC parity): a compaction that *worked* but
|
|
174
|
+
#: whose freed space refills within ``rapid_refill_window_rounds``
|
|
175
|
+
#: rounds counts as a refill; ``max_rapid_refills`` consecutive
|
|
176
|
+
#: refills open the circuit — the transcript is churning faster than
|
|
177
|
+
#: compaction can help, and each rewrite only kills the prompt-cache
|
|
178
|
+
#: prefix. Distinct from the failure breaker above, which catches
|
|
179
|
+
#: compactions that never get under threshold at all.
|
|
180
|
+
self.rapid_refill_window_rounds = rapid_refill_window_rounds
|
|
181
|
+
self.max_rapid_refills = max_rapid_refills
|
|
182
|
+
self._rapid_refills = 0
|
|
183
|
+
self._last_compact_round: int | None = None
|
|
158
184
|
# Observability: True once the breaker tripped; the pressure path
|
|
159
185
|
# stops firing for the rest of the session.
|
|
160
186
|
self.circuit_open = False
|
|
187
|
+
#: Which breaker tripped: "consecutive_failures" | "rapid_refill".
|
|
188
|
+
self.circuit_reason: str | None = None
|
|
161
189
|
|
|
162
190
|
def _estimate(self, transcript: Sequence[LLMMessage]) -> int:
|
|
163
191
|
return estimate_tokens(transcript, model=self._model)
|
|
@@ -214,9 +242,10 @@ class CompactionHooks(NoopHooks):
|
|
|
214
242
|
)
|
|
215
243
|
pressure = self._pressure(transcript, ctx)
|
|
216
244
|
if pressure < self._threshold * self._max_tokens:
|
|
217
|
-
# A healthy round resets
|
|
218
|
-
# the transcript that caused it) is gone.
|
|
245
|
+
# A healthy round resets both breaker counts — the pathology
|
|
246
|
+
# (or the transcript that caused it) is gone.
|
|
219
247
|
self._consecutive_failures = 0
|
|
248
|
+
self._rapid_refills = 0
|
|
220
249
|
return PreStepAction(kind="proceed")
|
|
221
250
|
if self.circuit_open:
|
|
222
251
|
# Breaker tripped: further pressure rewrites only invalidate the
|
|
@@ -234,6 +263,7 @@ class CompactionHooks(NoopHooks):
|
|
|
234
263
|
if self._estimate(compacted) < threshold:
|
|
235
264
|
self.compactions += 1
|
|
236
265
|
self._consecutive_failures = 0
|
|
266
|
+
notice = self._note_successful_compaction(round_index)
|
|
237
267
|
self._last_compaction_pressure = pressure
|
|
238
268
|
self._reset_observed(ctx)
|
|
239
269
|
return PreStepAction(
|
|
@@ -245,13 +275,17 @@ class CompactionHooks(NoopHooks):
|
|
|
245
275
|
pre_tokens=pressure,
|
|
246
276
|
post_tokens=self._estimate(compacted),
|
|
247
277
|
),
|
|
278
|
+
appends=[notice] if notice is not None else None,
|
|
279
|
+
append_action="reminder" if notice is not None else None,
|
|
248
280
|
)
|
|
249
281
|
|
|
250
282
|
compacted = await self._summarize_middle(compacted)
|
|
251
283
|
post = self._estimate(compacted)
|
|
252
284
|
self.compactions += 1
|
|
285
|
+
notice: TranscriptAppend | None = None
|
|
253
286
|
if post < threshold:
|
|
254
287
|
self._consecutive_failures = 0
|
|
288
|
+
notice = self._note_successful_compaction(round_index)
|
|
255
289
|
else:
|
|
256
290
|
# Ineffective: even fold+summarize stayed over threshold (e.g. a
|
|
257
291
|
# single kept tool result bigger than the window). Three in a
|
|
@@ -259,6 +293,7 @@ class CompactionHooks(NoopHooks):
|
|
|
259
293
|
self._consecutive_failures += 1
|
|
260
294
|
if self._consecutive_failures >= self.max_consecutive_failures:
|
|
261
295
|
self.circuit_open = True
|
|
296
|
+
self.circuit_reason = "consecutive_failures"
|
|
262
297
|
self._last_compaction_pressure = pressure
|
|
263
298
|
self._reset_observed(ctx)
|
|
264
299
|
return PreStepAction(
|
|
@@ -270,20 +305,52 @@ class CompactionHooks(NoopHooks):
|
|
|
270
305
|
pre_tokens=pressure,
|
|
271
306
|
post_tokens=post,
|
|
272
307
|
),
|
|
308
|
+
appends=[notice] if notice is not None else None,
|
|
309
|
+
append_action="reminder" if notice is not None else None,
|
|
273
310
|
)
|
|
274
311
|
|
|
312
|
+
def _note_successful_compaction(self, round_index: int) -> TranscriptAppend | None:
|
|
313
|
+
"""Rapid-refill bookkeeping for a compaction that landed under
|
|
314
|
+
threshold. A refill is a successful compaction within
|
|
315
|
+
``rapid_refill_window_rounds`` of the previous one; on the
|
|
316
|
+
``max_rapid_refills``-th consecutive refill the circuit opens and
|
|
317
|
+
the round carries the thrashing reminder so both the model and the
|
|
318
|
+
host UI see the actionable notice."""
|
|
319
|
+
if (
|
|
320
|
+
self._last_compact_round is not None
|
|
321
|
+
and round_index - self._last_compact_round <= self.rapid_refill_window_rounds
|
|
322
|
+
):
|
|
323
|
+
self._rapid_refills += 1
|
|
324
|
+
else:
|
|
325
|
+
self._rapid_refills = 0
|
|
326
|
+
self._last_compact_round = round_index
|
|
327
|
+
if self._rapid_refills >= self.max_rapid_refills and not self.circuit_open:
|
|
328
|
+
self.circuit_open = True
|
|
329
|
+
self.circuit_reason = "rapid_refill"
|
|
330
|
+
reminder = CompactionThrashingReminder(
|
|
331
|
+
self._rapid_refills, self.rapid_refill_window_rounds
|
|
332
|
+
)
|
|
333
|
+
return TranscriptAppend(
|
|
334
|
+
message=LLMMessage.text_of(reminder.role, reminder.render()),
|
|
335
|
+
kind=reminder.content_kind,
|
|
336
|
+
fragment=reminder,
|
|
337
|
+
)
|
|
338
|
+
return None
|
|
339
|
+
|
|
275
340
|
async def compact_now(self, transcript: list[LLMMessage], ctx: Any) -> PreStepAction:
|
|
276
341
|
"""Manual compaction (CC ``/compact`` parity): fold old tool results,
|
|
277
342
|
then summarize the middle, regardless of pressure, hysteresis, or the
|
|
278
343
|
circuit breaker — the user asked for it. Returns a ``proceed`` action
|
|
279
344
|
carrying the rewrite; when neither stage changes anything the action
|
|
280
345
|
carries no rewrite (nothing was worth invalidating the cache for).
|
|
281
|
-
A manual pass resets
|
|
346
|
+
A manual pass resets both breaker counts: the user has taken over.
|
|
282
347
|
"""
|
|
283
348
|
pre = self._estimate(transcript)
|
|
284
349
|
compacted = self._fold_old_tool_results(transcript)
|
|
285
350
|
compacted = await self._summarize_middle(compacted)
|
|
286
351
|
self._consecutive_failures = 0
|
|
352
|
+
self._rapid_refills = 0
|
|
353
|
+
self._last_compact_round = None
|
|
287
354
|
if compacted is transcript or compacted == transcript:
|
|
288
355
|
return PreStepAction(kind="proceed", reason="compact: nothing to fold")
|
|
289
356
|
self.compactions += 1
|
|
@@ -209,6 +209,11 @@ class PressureCompaction:
|
|
|
209
209
|
# trades cache hits for a bounded transcript. R16 found compaction does not
|
|
210
210
|
# move the eval score, so this stays opt-in capability, not a default.
|
|
211
211
|
micro_compact_interval_rounds: int = 0
|
|
212
|
+
# Rapid-refill breaker (CC parity): a successful compaction whose freed
|
|
213
|
+
# space refills within this many rounds of the previous one counts as a
|
|
214
|
+
# refill; max_rapid_refills consecutive refills open the circuit.
|
|
215
|
+
rapid_refill_window_rounds: int = 3
|
|
216
|
+
max_rapid_refills: int = 3
|
|
212
217
|
name: str = "pressure_compaction"
|
|
213
218
|
assumes: str = (
|
|
214
219
|
"long trajectories exceed the window; older detail is expendable "
|
|
@@ -231,6 +236,8 @@ class PressureCompaction:
|
|
|
231
236
|
summarizer=provider,
|
|
232
237
|
model=self.model,
|
|
233
238
|
micro_compact_interval_rounds=self.micro_compact_interval_rounds,
|
|
239
|
+
rapid_refill_window_rounds=self.rapid_refill_window_rounds,
|
|
240
|
+
max_rapid_refills=self.max_rapid_refills,
|
|
234
241
|
**extra,
|
|
235
242
|
),
|
|
236
243
|
forward=("compact_now",),
|
|
@@ -158,7 +158,9 @@ from .errors import (
|
|
|
158
158
|
classify_http_status,
|
|
159
159
|
is_retryable,
|
|
160
160
|
)
|
|
161
|
+
from .google_genai import GoogleGenAIProvider
|
|
161
162
|
from .openai_compat import OpenAICompatProvider
|
|
163
|
+
from .openai_responses import OpenAIResponsesProvider
|
|
162
164
|
from .presets import (
|
|
163
165
|
PROVIDER_PRESETS,
|
|
164
166
|
PresetEntry,
|
|
@@ -174,6 +176,7 @@ __all__ = [
|
|
|
174
176
|
"RETRYABLE_KINDS",
|
|
175
177
|
"AnthropicProvider",
|
|
176
178
|
"ContentPart",
|
|
179
|
+
"GoogleGenAIProvider",
|
|
177
180
|
"ImagePart",
|
|
178
181
|
"LLMError",
|
|
179
182
|
"LLMErrorKind",
|
|
@@ -188,6 +191,7 @@ __all__ = [
|
|
|
188
191
|
"describe_compat_flags",
|
|
189
192
|
"describe_provider_presets",
|
|
190
193
|
"OpenAICompatProvider",
|
|
194
|
+
"OpenAIResponsesProvider",
|
|
191
195
|
"TextPart",
|
|
192
196
|
"classify_error",
|
|
193
197
|
"classify_http_status",
|