deepstrike 0.2.34__tar.gz → 0.2.35__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.
- {deepstrike-0.2.34 → deepstrike-0.2.35}/Cargo.lock +5 -5
- {deepstrike-0.2.34 → deepstrike-0.2.35}/Cargo.toml +2 -2
- {deepstrike-0.2.34 → deepstrike-0.2.35}/PKG-INFO +7 -1
- {deepstrike-0.2.34 → deepstrike-0.2.35}/README.md +6 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/proc/mod.rs +1 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/runtime/kernel.rs +28 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/scheduler/state_machine/eviction.rs +52 -1
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/scheduler/state_machine/mod.rs +54 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/scheduler/state_machine/tests.rs +139 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/types/result.rs +5 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/providers/anthropic.py +4 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/providers/openai.py +7 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/providers/stream.py +3 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/runner.py +28 -27
- {deepstrike-0.2.34 → deepstrike-0.2.35}/pyproject.toml +1 -1
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/Cargo.toml +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/context/compression.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/context/config.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/context/dashboard.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/context/manager.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/context/mod.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/context/partitions.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/context/pressure.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/context/renderer.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/context/renewal.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/context/sections.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/context/skill_catalog.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/context/snapshot.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/context/summarizer.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/context/task_state.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/context/text.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/context/token_engine.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/governance/audit.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/governance/constraint.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/governance/mod.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/governance/permission.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/governance/pipeline.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/governance/quota.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/governance/rate_limit.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/governance/sandbox.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/governance/tool_decision.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/governance/veto.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/harness/eval.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/harness/mod.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/lib.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/memory/curator.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/memory/durable.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/memory/extractor.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/memory/idle_pipeline.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/memory/mod.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/memory/runtime.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/memory/semantic.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/memory/session.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/memory/synthesis.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/memory/trace_analyzer.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/memory/working.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/mm/handle.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/mm/memory.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/mm/mod.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/orchestration/mod.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/orchestration/task_graph.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/orchestration/tournament.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/orchestration/workflow/mod.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/orchestration/workflow/run.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/runtime/event_log.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/runtime/mod.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/runtime/repair.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/runtime/replay.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/runtime/session.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/runtime/snapshot.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/scheduler/milestone.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/scheduler/mod.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/scheduler/policy.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/scheduler/rollback.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/scheduler/state_machine/capability.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/scheduler/state_machine/gate.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/scheduler/state_machine/milestone_exec.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/scheduler/state_machine/process.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/scheduler/state_machine/signal.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/scheduler/state_machine/workflow.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/scheduler/tcb.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/signals/attention.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/signals/mod.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/signals/queue.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/signals/router.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/syscall/mod.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/types/agent.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/types/capability.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/types/contract.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/types/error.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/types/message.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/types/milestone.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/types/mod.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/types/model.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/types/policy.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/types/signal.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/types/skill.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/types/task.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-py/Cargo.toml +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-py/src/lib.rs +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/__init__.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/collaboration/__init__.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/collaboration/contract.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/collaboration/handoff.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/collaboration/harness.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/collaboration/modes.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/collaboration/pool.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/governance.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/harness/__init__.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/harness/harness.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/harness/judge.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/kernel/__init__.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/knowledge/__init__.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/knowledge/source.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/memory/__init__.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/memory/agent.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/memory/in_memory_store.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/memory/protocols.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/memory/working.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/providers/__init__.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/providers/anthropic_compatible.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/providers/base.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/providers/deepseek.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/providers/factories.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/providers/gemini.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/providers/glm.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/providers/kimi.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/providers/minimax.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/providers/ollama.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/providers/openai_responses.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/providers/qwen.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/providers/replay.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/providers/replay_validator.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/providers/vendor_profiles.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/__init__.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/archive.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/credential_vault.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/eval.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/event_stream.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/execution_plane.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/facade.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/filtered_plane.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/kernel_event_log.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/kernel_step.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/large_result_spool.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/mcp_proxy_plane.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/os_profile.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/os_snapshot.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/output_schema.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/process_sandbox_plane.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/provider_replay.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/reactive_session.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/reducers.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/remote_vpc_plane.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/replay_fixture.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/replay_provider.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/replay_sanitize.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/run_group.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/session_log.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/session_repair.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/sub_agent_orchestrator.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/turn_policy.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/workflow_control_flow.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/workflow_store.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/runtime/worktree_plane.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/safety/__init__.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/safety/permissions.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/signals/__init__.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/signals/gateway.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/signals/scheduled.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/signals/types.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/skills/__init__.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/skills/registry.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/skills/watcher.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/tools/__init__.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/tools/builtin/__init__.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/tools/builtin/read_file.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/tools/errors.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/tools/execution.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/tools/registry.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/types/__init__.py +0 -0
- {deepstrike-0.2.34 → deepstrike-0.2.35}/deepstrike/types/agent.py +0 -0
|
@@ -194,7 +194,7 @@ dependencies = [
|
|
|
194
194
|
|
|
195
195
|
[[package]]
|
|
196
196
|
name = "deepstrike-core"
|
|
197
|
-
version = "0.2.
|
|
197
|
+
version = "0.2.35"
|
|
198
198
|
dependencies = [
|
|
199
199
|
"compact_str",
|
|
200
200
|
"pretty_assertions",
|
|
@@ -206,7 +206,7 @@ dependencies = [
|
|
|
206
206
|
|
|
207
207
|
[[package]]
|
|
208
208
|
name = "deepstrike-node"
|
|
209
|
-
version = "0.2.
|
|
209
|
+
version = "0.2.35"
|
|
210
210
|
dependencies = [
|
|
211
211
|
"compact_str",
|
|
212
212
|
"deepstrike-core",
|
|
@@ -218,7 +218,7 @@ dependencies = [
|
|
|
218
218
|
|
|
219
219
|
[[package]]
|
|
220
220
|
name = "deepstrike-py"
|
|
221
|
-
version = "0.2.
|
|
221
|
+
version = "0.2.35"
|
|
222
222
|
dependencies = [
|
|
223
223
|
"compact_str",
|
|
224
224
|
"deepstrike-core",
|
|
@@ -229,7 +229,7 @@ dependencies = [
|
|
|
229
229
|
|
|
230
230
|
[[package]]
|
|
231
231
|
name = "deepstrike-sdk"
|
|
232
|
-
version = "0.2.
|
|
232
|
+
version = "0.2.35"
|
|
233
233
|
dependencies = [
|
|
234
234
|
"async-stream",
|
|
235
235
|
"async-trait",
|
|
@@ -263,7 +263,7 @@ dependencies = [
|
|
|
263
263
|
|
|
264
264
|
[[package]]
|
|
265
265
|
name = "deepstrike-wasm"
|
|
266
|
-
version = "0.2.
|
|
266
|
+
version = "0.2.35"
|
|
267
267
|
dependencies = [
|
|
268
268
|
"compact_str",
|
|
269
269
|
"deepstrike-core",
|
|
@@ -3,13 +3,13 @@ resolver = "2"
|
|
|
3
3
|
members = ["crates/deepstrike-core", "crates/deepstrike-py"]
|
|
4
4
|
|
|
5
5
|
[workspace.package]
|
|
6
|
-
version = "0.2.
|
|
6
|
+
version = "0.2.35"
|
|
7
7
|
edition = "2024"
|
|
8
8
|
license = "MIT"
|
|
9
9
|
repository = "https://github.com/kongusen/deepstrike"
|
|
10
10
|
|
|
11
11
|
[workspace.dependencies]
|
|
12
|
-
deepstrike-core = { path = "crates/deepstrike-core", version = "0.2.
|
|
12
|
+
deepstrike-core = { path = "crates/deepstrike-core", version = "0.2.35" }
|
|
13
13
|
notify = "6"
|
|
14
14
|
serde = { version = "1", features = ["derive"] }
|
|
15
15
|
serde_json = "1"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deepstrike
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.35
|
|
4
4
|
Requires-Dist: httpx>=0.27
|
|
5
5
|
Requires-Dist: pyyaml>=6.0
|
|
6
6
|
Requires-Dist: anyio>=4.0
|
|
@@ -11,6 +11,12 @@ Requires-Dist: dashscope>=1.20
|
|
|
11
11
|
Requires-Python: >=3.10
|
|
12
12
|
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
13
13
|
|
|
14
|
+
<p align="center">
|
|
15
|
+
<a href="https://github.com/kongusen/deepstrike">
|
|
16
|
+
<img src="https://raw.githubusercontent.com/kongusen/deepstrike/main/docs/public/banner.png" alt="DeepStrike" width="420" />
|
|
17
|
+
</a>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
14
20
|
# DeepStrike Python SDK
|
|
15
21
|
|
|
16
22
|
Runtime framework built on a Rust kernel. The kernel owns loop control, context compression, governance, signal routing, and memory paging — the SDK owns all I/O (LLM calls, tool execution, disk, long-term memory).
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://github.com/kongusen/deepstrike">
|
|
3
|
+
<img src="https://raw.githubusercontent.com/kongusen/deepstrike/main/docs/public/banner.png" alt="DeepStrike" width="420" />
|
|
4
|
+
</a>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
1
7
|
# DeepStrike Python SDK
|
|
2
8
|
|
|
3
9
|
Runtime framework built on a Rust kernel. The kernel owns loop control, context compression, governance, signal routing, and memory paging — the SDK owns all I/O (LLM calls, tool execution, disk, long-term memory).
|
|
@@ -346,10 +346,24 @@ pub enum KernelInputEvent {
|
|
|
346
346
|
// on a 0 clock (effectively unlimited). Can become required once all SDKs feed time.
|
|
347
347
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
348
348
|
now_ms: Option<u64>,
|
|
349
|
+
/// Provider stop_reason for this response — `max_tokens` (Anthropic) / `length` (OpenAI)
|
|
350
|
+
/// signal an output-cap truncation, which drives the kernel's max-output-tokens recovery.
|
|
351
|
+
/// Additive: omitted by providers/SDKs that don't report it (no-op recovery).
|
|
352
|
+
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
353
|
+
stop_reason: Option<String>,
|
|
349
354
|
},
|
|
350
355
|
ToolResults {
|
|
351
356
|
results: Vec<ToolResult>,
|
|
352
357
|
},
|
|
358
|
+
/// Reactive recovery entry point: the SDK's provider stream failed. The kernel classifies the
|
|
359
|
+
/// error (context-overflow vs other) and runs the bounded compact-and-retry recovery ladder,
|
|
360
|
+
/// returning `CallProvider` to retry with a freshly compacted context or `Done` to terminate.
|
|
361
|
+
/// The runners forward the raw provider error text and dispatch the result, instead of each
|
|
362
|
+
/// owning the classify + compact + retry + give-up policy. Additive ABI: a brand-new variant,
|
|
363
|
+
/// byte-identical on the wire for SDKs that never send it.
|
|
364
|
+
ProviderError {
|
|
365
|
+
message: String,
|
|
366
|
+
},
|
|
353
367
|
Signal {
|
|
354
368
|
signal: RuntimeSignal,
|
|
355
369
|
},
|
|
@@ -994,6 +1008,7 @@ impl KernelRuntime {
|
|
|
994
1008
|
observed_input_tokens,
|
|
995
1009
|
observed_output_tokens: _,
|
|
996
1010
|
now_ms,
|
|
1011
|
+
stop_reason,
|
|
997
1012
|
} => {
|
|
998
1013
|
if let Some(tokens) = observed_input_tokens {
|
|
999
1014
|
self.sm.ctx.set_observed_prompt_tokens(tokens);
|
|
@@ -1003,11 +1018,18 @@ impl KernelRuntime {
|
|
|
1003
1018
|
if let Some(ms) = now_ms {
|
|
1004
1019
|
self.sm.set_observed_time(ms);
|
|
1005
1020
|
}
|
|
1021
|
+
// Stash stop_reason so `feed` can detect an output-cap truncation and drive recovery.
|
|
1022
|
+
self.sm.set_pending_stop_reason(stop_reason);
|
|
1006
1023
|
self.sm.feed(LoopEvent::LLMResponse { message })
|
|
1007
1024
|
}
|
|
1008
1025
|
KernelInputEvent::ToolResults { results } => {
|
|
1009
1026
|
self.sm.feed(LoopEvent::ToolResults { results })
|
|
1010
1027
|
}
|
|
1028
|
+
KernelInputEvent::ProviderError { message } => {
|
|
1029
|
+
// Reactive recovery is a kernel decision: classify + bounded compact-and-retry,
|
|
1030
|
+
// returning the next action (retry or honest terminal) through the common tail.
|
|
1031
|
+
self.sm.recover_from_provider_error(&message)
|
|
1032
|
+
}
|
|
1011
1033
|
KernelInputEvent::Signal { signal } => match self.sm.signal_event(signal) {
|
|
1012
1034
|
Some(action) => action,
|
|
1013
1035
|
// Non-actionable disposition (queued / observed / ignored / dropped):
|
|
@@ -1232,6 +1254,7 @@ mod tests {
|
|
|
1232
1254
|
message: Message::assistant("done"),
|
|
1233
1255
|
observed_input_tokens: None,
|
|
1234
1256
|
observed_output_tokens: None,
|
|
1257
|
+
stop_reason: None,
|
|
1235
1258
|
now_ms: None,
|
|
1236
1259
|
}));
|
|
1237
1260
|
|
|
@@ -1450,6 +1473,7 @@ mod tests {
|
|
|
1450
1473
|
message: msg,
|
|
1451
1474
|
observed_input_tokens: None,
|
|
1452
1475
|
observed_output_tokens: None,
|
|
1476
|
+
stop_reason: None,
|
|
1453
1477
|
now_ms: None,
|
|
1454
1478
|
}));
|
|
1455
1479
|
runtime.step(KernelInput::new(KernelInputEvent::ToolResults {
|
|
@@ -1717,6 +1741,7 @@ mod tests {
|
|
|
1717
1741
|
message: msg,
|
|
1718
1742
|
observed_input_tokens: None,
|
|
1719
1743
|
observed_output_tokens: None,
|
|
1744
|
+
stop_reason: None,
|
|
1720
1745
|
now_ms: Some(100),
|
|
1721
1746
|
}));
|
|
1722
1747
|
let step = runtime.step(KernelInput::new(KernelInputEvent::ToolResults {
|
|
@@ -1767,6 +1792,7 @@ mod tests {
|
|
|
1767
1792
|
message: assistant_calling(tool),
|
|
1768
1793
|
observed_input_tokens: None,
|
|
1769
1794
|
observed_output_tokens: None,
|
|
1795
|
+
stop_reason: None,
|
|
1770
1796
|
now_ms: None,
|
|
1771
1797
|
}))
|
|
1772
1798
|
}
|
|
@@ -1992,6 +2018,7 @@ mod tests {
|
|
|
1992
2018
|
message: assistant_calling("fetch"),
|
|
1993
2019
|
observed_input_tokens: None,
|
|
1994
2020
|
observed_output_tokens: None,
|
|
2021
|
+
stop_reason: None,
|
|
1995
2022
|
now_ms: Some(1_000),
|
|
1996
2023
|
}));
|
|
1997
2024
|
assert!(
|
|
@@ -2011,6 +2038,7 @@ mod tests {
|
|
|
2011
2038
|
message: assistant_calling("fetch"),
|
|
2012
2039
|
observed_input_tokens: None,
|
|
2013
2040
|
observed_output_tokens: None,
|
|
2041
|
+
stop_reason: None,
|
|
2014
2042
|
now_ms: Some(1_001),
|
|
2015
2043
|
}));
|
|
2016
2044
|
assert!(
|
|
@@ -1,12 +1,63 @@
|
|
|
1
1
|
//! MM / eviction execution impl for [`super::LoopStateMachine`].
|
|
2
2
|
|
|
3
|
-
use super::{KernelObservation, LoopStateMachine};
|
|
3
|
+
use super::{KernelObservation, LoopAction, LoopPhase, LoopStateMachine};
|
|
4
4
|
use crate::context::pressure::PressureAction;
|
|
5
5
|
use crate::mm::{page_in_requests_from_calls, tier_hint_for_compress};
|
|
6
6
|
use crate::runtime::kernel::KernelPressureAction;
|
|
7
7
|
use crate::types::message::{Message, ToolCall};
|
|
8
|
+
use crate::types::result::TerminationReason;
|
|
9
|
+
|
|
10
|
+
/// Max consecutive compact-and-retry attempts before a context overflow is declared
|
|
11
|
+
/// unrecoverable. Bounds the reactive recovery ladder (anti-spiral); resets on any successful
|
|
12
|
+
/// provider turn. The `force_compress` "nothing left to save" check is the real terminator —
|
|
13
|
+
/// this is the belt-and-suspenders cap so a degenerate provider that 413s forever still ends.
|
|
14
|
+
const MAX_RECOVERY_ATTEMPTS: u8 = 2;
|
|
15
|
+
|
|
16
|
+
/// Classify a provider error message as a context-overflow (prompt-too-long / 413). Centralizes
|
|
17
|
+
/// the case-insensitive string match the four SDK runners (node/python/rust/wasm) each used to
|
|
18
|
+
/// own, so the recovery vocabulary lives in exactly one place.
|
|
19
|
+
pub(crate) fn is_prompt_too_long(message: &str) -> bool {
|
|
20
|
+
let msg = message.to_lowercase();
|
|
21
|
+
msg.contains("413")
|
|
22
|
+
|| msg.contains("too long")
|
|
23
|
+
|| msg.contains("prompt too long")
|
|
24
|
+
|| msg.contains("context length exceeded")
|
|
25
|
+
|| msg.contains("context_length_exceeded")
|
|
26
|
+
}
|
|
8
27
|
|
|
9
28
|
impl LoopStateMachine {
|
|
29
|
+
/// Reactive recovery for a provider error the SDK reports via [`KernelInputEvent::ProviderError`].
|
|
30
|
+
/// Owns the policy the SDK runners used to duplicate: classify → compact-and-retry on overflow
|
|
31
|
+
/// (bounded, anti-spiral) → honest terminal when the ladder is exhausted. Returns the next
|
|
32
|
+
/// [`LoopAction`] so the kernel's normal action tail dispatches it: `CallLLM` to retry the
|
|
33
|
+
/// provider with a freshly compacted context, or `Done { ContextOverflow }` to give up.
|
|
34
|
+
///
|
|
35
|
+
/// This is the reactive twin of the proactive eviction checkpoint in `feed` — same execution
|
|
36
|
+
/// funnel (`force_compact` → `EvictionOp`s), now driven by a real provider 413 instead of an
|
|
37
|
+
/// rho threshold, and surfaced as a kernel decision rather than SDK control flow.
|
|
38
|
+
pub fn recover_from_provider_error(&mut self, message: &str) -> LoopAction {
|
|
39
|
+
self.observations.clear();
|
|
40
|
+
if !is_prompt_too_long(message) {
|
|
41
|
+
// Non-overflow provider failures aren't recoverable here — terminate with `Error`,
|
|
42
|
+
// the same outcome the runners produced, minus the fabricated `timeout`.
|
|
43
|
+
return self.terminate(TerminationReason::Error, None);
|
|
44
|
+
}
|
|
45
|
+
if self.recovery_attempts >= MAX_RECOVERY_ATTEMPTS {
|
|
46
|
+
return self.terminate(TerminationReason::ContextOverflow, None);
|
|
47
|
+
}
|
|
48
|
+
self.recovery_attempts += 1;
|
|
49
|
+
if self.force_compact() {
|
|
50
|
+
// Recovered headroom: re-render and retry as a normal turn (tools intact). The
|
|
51
|
+
// Compressed/PageOut observations pushed by `force_compact` ride out in this same
|
|
52
|
+
// step, so the SDK still archives the evicted messages.
|
|
53
|
+
self.phase = LoopPhase::Reason;
|
|
54
|
+
self.emit_call_llm()
|
|
55
|
+
} else {
|
|
56
|
+
// Nothing left to compact — the prompt genuinely won't fit. Honest terminal.
|
|
57
|
+
self.terminate(TerminationReason::ContextOverflow, None)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
10
61
|
/// 强行进行一次最大力度的压缩归档。通常用于收到模型 API 413 (Prompt too long) 时做兜底重试。
|
|
11
62
|
pub fn force_compact(&mut self) -> bool {
|
|
12
63
|
let action = PressureAction::AutoCompact;
|
{deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/scheduler/state_machine/mod.rs
RENAMED
|
@@ -188,6 +188,21 @@ pub struct LoopStateMachine {
|
|
|
188
188
|
/// When set, the next LLM call strips tools to force a text response,
|
|
189
189
|
/// then terminates with this reason once the response arrives.
|
|
190
190
|
pub(super) pending_termination: Option<TerminationReason>,
|
|
191
|
+
/// Reactive context-overflow recovery: consecutive compact-and-retry attempts since the last
|
|
192
|
+
/// successful provider turn. Bounds the recovery ladder (anti-spiral) and resets to 0 on any
|
|
193
|
+
/// `LLMResponse`, mirroring the per-turn `hasAttemptedReactiveCompact` reset the SDK runners
|
|
194
|
+
/// used to own. See `recover_from_provider_error`.
|
|
195
|
+
pub(super) recovery_attempts: u8,
|
|
196
|
+
/// Max-output-tokens recovery: consecutive continue-and-retry turns since the model last
|
|
197
|
+
/// finished a response WITHOUT hitting the output cap. When a turn is cut off at the cap
|
|
198
|
+
/// (provider `stop_reason` = max_tokens/length) the kernel keeps the partial, nudges the model
|
|
199
|
+
/// to resume mid-thought, and re-calls — bounded by `MAX_OUTPUT_RECOVERY` (mirrors query.ts's
|
|
200
|
+
/// MAX_OUTPUT_TOKENS_RECOVERY_LIMIT). Resets to 0 on any non-truncated response.
|
|
201
|
+
pub(super) output_recovery_attempts: u8,
|
|
202
|
+
/// Transient carrier for the provider `stop_reason` of the in-flight response, set by the
|
|
203
|
+
/// kernel ABI just before `feed(LLMResponse)` and taken (cleared) inside it. `None` when the
|
|
204
|
+
/// SDK/provider doesn't report one (every non-Anthropic provider today ⇒ no-op).
|
|
205
|
+
pub(super) pending_stop_reason: Option<String>,
|
|
191
206
|
/// Number of history messages present at session start (after preload_history).
|
|
192
207
|
/// drain_new_messages() returns the slice from this offset onward.
|
|
193
208
|
pub(super) session_history_baseline: usize,
|
|
@@ -265,6 +280,9 @@ impl LoopStateMachine {
|
|
|
265
280
|
group_tokens_base: 0,
|
|
266
281
|
group_spawns_base: 0,
|
|
267
282
|
pending_termination: None,
|
|
283
|
+
recovery_attempts: 0,
|
|
284
|
+
output_recovery_attempts: 0,
|
|
285
|
+
pending_stop_reason: None,
|
|
268
286
|
session_history_baseline: 0,
|
|
269
287
|
checkpoint: TurnCheckpoint::default(),
|
|
270
288
|
milestone: MilestoneTracker::new(),
|
|
@@ -393,6 +411,12 @@ impl LoopStateMachine {
|
|
|
393
411
|
}
|
|
394
412
|
}
|
|
395
413
|
|
|
414
|
+
/// Stash the in-flight response's provider `stop_reason` so `feed(LLMResponse)` can detect an
|
|
415
|
+
/// output-cap truncation. Set by the kernel ABI right before feeding the result; `None` clears it.
|
|
416
|
+
pub fn set_pending_stop_reason(&mut self, stop_reason: Option<String>) {
|
|
417
|
+
self.pending_stop_reason = stop_reason;
|
|
418
|
+
}
|
|
419
|
+
|
|
396
420
|
/// Pre-populate the history partition with messages from a prior session.
|
|
397
421
|
///
|
|
398
422
|
/// Call **before** `start()` when resuming a conversation. Sets the baseline
|
|
@@ -464,14 +488,41 @@ impl LoopStateMachine {
|
|
|
464
488
|
LoopEvent::Start { task } => self.start(task),
|
|
465
489
|
|
|
466
490
|
LoopEvent::LLMResponse { message } => {
|
|
491
|
+
// A response arrived ⇒ the prompt fit ⇒ the overflow recovery ladder is reset.
|
|
492
|
+
self.recovery_attempts = 0;
|
|
467
493
|
let tokens = self.message_tokens(&message);
|
|
468
494
|
self.total_tokens += tokens as u64;
|
|
469
495
|
|
|
496
|
+
// Max-output-tokens recovery (mirrors query.ts): a response cut off at the output
|
|
497
|
+
// cap reports stop_reason = max_tokens (Anthropic) / length (OpenAI). A clean finish
|
|
498
|
+
// resets the ladder.
|
|
499
|
+
const MAX_OUTPUT_RECOVERY: u8 = 3;
|
|
500
|
+
const OUTPUT_TRUNCATION_NUDGE: &str = "Output token limit hit. Resume directly — no apology, no recap of what you were doing. Pick up mid-thought if that is where the cut happened. Break remaining work into smaller pieces.";
|
|
501
|
+
let truncated = matches!(
|
|
502
|
+
self.pending_stop_reason.take().as_deref(),
|
|
503
|
+
Some("max_tokens") | Some("length"),
|
|
504
|
+
);
|
|
505
|
+
if !truncated {
|
|
506
|
+
self.output_recovery_attempts = 0;
|
|
507
|
+
}
|
|
508
|
+
|
|
470
509
|
if let Some(reason) = self.pending_termination.take() {
|
|
471
510
|
return self.terminate(reason, Some(message));
|
|
472
511
|
}
|
|
473
512
|
|
|
474
513
|
if message.tool_calls.is_empty() {
|
|
514
|
+
// The model was cut off at the output cap with no tool call. Keep the partial,
|
|
515
|
+
// nudge it to resume mid-thought, and re-call — instead of mistaking the
|
|
516
|
+
// truncation for a finished turn. Bounded by MAX_OUTPUT_RECOVERY; once exhausted
|
|
517
|
+
// the partial stands and the turn terminates normally below. (A truncated
|
|
518
|
+
// *tool-call* turn isn't handled here — it falls through to tool execution.)
|
|
519
|
+
if truncated && self.output_recovery_attempts < MAX_OUTPUT_RECOVERY {
|
|
520
|
+
self.output_recovery_attempts += 1;
|
|
521
|
+
self.ctx.push_history(message, tokens);
|
|
522
|
+
self.ctx.push_signal(OUTPUT_TRUNCATION_NUDGE.to_string());
|
|
523
|
+
self.phase = LoopPhase::Reason;
|
|
524
|
+
return self.emit_call_llm();
|
|
525
|
+
}
|
|
475
526
|
// When a milestone contract is active and not yet complete,
|
|
476
527
|
// request evaluation instead of terminating.
|
|
477
528
|
if !self.milestone.is_complete() {
|
|
@@ -795,6 +846,9 @@ impl LoopStateMachine {
|
|
|
795
846
|
group_tokens_base: 0,
|
|
796
847
|
group_spawns_base: 0,
|
|
797
848
|
pending_termination: None,
|
|
849
|
+
recovery_attempts: 0,
|
|
850
|
+
output_recovery_attempts: 0,
|
|
851
|
+
pending_stop_reason: None,
|
|
798
852
|
session_history_baseline: 0,
|
|
799
853
|
checkpoint: TurnCheckpoint::default(),
|
|
800
854
|
milestone: crate::scheduler::milestone::MilestoneTracker::new(),
|
{deepstrike-0.2.34 → deepstrike-0.2.35}/crates/deepstrike-core/src/scheduler/state_machine/tests.rs
RENAMED
|
@@ -537,6 +537,145 @@
|
|
|
537
537
|
assert!(obs.iter().any(|o| matches!(o, KernelObservation::PageOut { .. })));
|
|
538
538
|
}
|
|
539
539
|
|
|
540
|
+
// ---- Reactive recovery ladder (lifted from the SDK runners into the kernel) ----
|
|
541
|
+
|
|
542
|
+
fn compactible_machine() -> LoopStateMachine {
|
|
543
|
+
let mut sm = LoopStateMachine::new(LoopPolicy {
|
|
544
|
+
max_tokens: 100,
|
|
545
|
+
max_turns: 100,
|
|
546
|
+
..LoopPolicy::default()
|
|
547
|
+
});
|
|
548
|
+
sm.start(RuntimeTask::new("test"));
|
|
549
|
+
for i in 0..10 {
|
|
550
|
+
sm.ctx.push_history(Message::user(format!("filler {i}")), 50);
|
|
551
|
+
}
|
|
552
|
+
sm
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
#[test]
|
|
556
|
+
fn recover_overflow_compacts_and_retries() {
|
|
557
|
+
let mut sm = compactible_machine();
|
|
558
|
+
let action = sm.recover_from_provider_error("HTTP 413: prompt is too long");
|
|
559
|
+
// Recovered headroom ⇒ retry the provider with a freshly compacted context.
|
|
560
|
+
assert!(matches!(action, LoopAction::CallLLM { .. }));
|
|
561
|
+
assert_eq!(sm.recovery_attempts, 1);
|
|
562
|
+
// The eviction rode out as observations so the SDK still archives the evicted messages.
|
|
563
|
+
let obs = sm.take_observations();
|
|
564
|
+
assert!(obs.iter().any(|o| matches!(o, KernelObservation::Compressed { .. })));
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
#[test]
|
|
568
|
+
fn recover_overflow_exhausted_terminates_context_overflow() {
|
|
569
|
+
// Fresh machine with nothing compactible ⇒ force_compact saves 0 ⇒ honest terminal.
|
|
570
|
+
let mut sm = sm();
|
|
571
|
+
sm.start(RuntimeTask::new("test"));
|
|
572
|
+
let action = sm.recover_from_provider_error("context_length_exceeded");
|
|
573
|
+
match action {
|
|
574
|
+
LoopAction::Done { result } => {
|
|
575
|
+
assert_eq!(result.termination, TerminationReason::ContextOverflow);
|
|
576
|
+
}
|
|
577
|
+
other => panic!("expected Done(ContextOverflow), got {other:?}"),
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
#[test]
|
|
582
|
+
fn recover_non_overflow_terminates_error() {
|
|
583
|
+
let mut sm = compactible_machine();
|
|
584
|
+
let action = sm.recover_from_provider_error("500 Internal Server Error");
|
|
585
|
+
match action {
|
|
586
|
+
LoopAction::Done { result } => {
|
|
587
|
+
assert_eq!(result.termination, TerminationReason::Error);
|
|
588
|
+
}
|
|
589
|
+
other => panic!("expected Done(Error), got {other:?}"),
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
#[test]
|
|
594
|
+
fn recover_attempt_cap_is_bounded() {
|
|
595
|
+
// Even a provider that 413s forever must terminate, not loop. Drive past the cap and
|
|
596
|
+
// assert a terminal appears within a bounded number of attempts.
|
|
597
|
+
let mut sm = compactible_machine();
|
|
598
|
+
let mut terminated = false;
|
|
599
|
+
for _ in 0..6 {
|
|
600
|
+
if let LoopAction::Done { result } = sm.recover_from_provider_error("413 too long") {
|
|
601
|
+
assert_eq!(result.termination, TerminationReason::ContextOverflow);
|
|
602
|
+
terminated = true;
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
assert!(terminated, "recovery ladder must terminate under repeated overflow");
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
#[test]
|
|
610
|
+
fn recovery_attempts_reset_on_successful_response() {
|
|
611
|
+
let mut sm = compactible_machine();
|
|
612
|
+
let action = sm.recover_from_provider_error("413 too long");
|
|
613
|
+
assert!(matches!(action, LoopAction::CallLLM { .. }));
|
|
614
|
+
assert_eq!(sm.recovery_attempts, 1);
|
|
615
|
+
// A response that fits resets the ladder (mirrors the per-turn SDK guard reset).
|
|
616
|
+
sm.feed(LoopEvent::LLMResponse { message: Message::assistant("recovered") });
|
|
617
|
+
assert_eq!(sm.recovery_attempts, 0);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
#[test]
|
|
621
|
+
fn is_prompt_too_long_classifier() {
|
|
622
|
+
use super::eviction::is_prompt_too_long;
|
|
623
|
+
assert!(is_prompt_too_long("Error 413"));
|
|
624
|
+
assert!(is_prompt_too_long("prompt is TOO LONG"));
|
|
625
|
+
assert!(is_prompt_too_long("context_length_exceeded"));
|
|
626
|
+
assert!(!is_prompt_too_long("429 rate limited"));
|
|
627
|
+
assert!(!is_prompt_too_long("connection reset"));
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// ---- Max-output-tokens recovery (Phase 4) ----
|
|
631
|
+
|
|
632
|
+
#[test]
|
|
633
|
+
fn truncated_response_continues_then_resets() {
|
|
634
|
+
let mut sm = sm();
|
|
635
|
+
sm.start(RuntimeTask::new("write a long thing"));
|
|
636
|
+
// Cut off at the output cap with no tool call ⇒ keep the partial and re-call (don't finish).
|
|
637
|
+
sm.set_pending_stop_reason(Some("max_tokens".into()));
|
|
638
|
+
let action = sm.feed(LoopEvent::LLMResponse { message: Message::assistant("partial...") });
|
|
639
|
+
assert!(matches!(action, LoopAction::CallLLM { .. }));
|
|
640
|
+
assert_eq!(sm.output_recovery_attempts, 1);
|
|
641
|
+
// A clean finish (no stop_reason) terminates normally AND resets the ladder.
|
|
642
|
+
let action = sm.feed(LoopEvent::LLMResponse { message: Message::assistant("...the rest. done") });
|
|
643
|
+
match action {
|
|
644
|
+
LoopAction::Done { result } => assert_eq!(result.termination, TerminationReason::Completed),
|
|
645
|
+
other => panic!("expected Done(Completed), got {other:?}"),
|
|
646
|
+
}
|
|
647
|
+
assert_eq!(sm.output_recovery_attempts, 0);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
#[test]
|
|
651
|
+
fn truncation_recovery_is_bounded() {
|
|
652
|
+
let mut sm = sm();
|
|
653
|
+
sm.start(RuntimeTask::new("write forever"));
|
|
654
|
+
// 3 attempts continue; the 4th consecutive truncation gives up and accepts the partial.
|
|
655
|
+
for _ in 0..3 {
|
|
656
|
+
sm.set_pending_stop_reason(Some("length".into()));
|
|
657
|
+
let action = sm.feed(LoopEvent::LLMResponse { message: Message::assistant("more") });
|
|
658
|
+
assert!(matches!(action, LoopAction::CallLLM { .. }));
|
|
659
|
+
}
|
|
660
|
+
sm.set_pending_stop_reason(Some("length".into()));
|
|
661
|
+
let action = sm.feed(LoopEvent::LLMResponse { message: Message::assistant("more") });
|
|
662
|
+
match action {
|
|
663
|
+
LoopAction::Done { result } => assert_eq!(result.termination, TerminationReason::Completed),
|
|
664
|
+
other => panic!("expected Done(Completed) after the cap, got {other:?}"),
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
#[test]
|
|
669
|
+
fn no_stop_reason_terminates_normally() {
|
|
670
|
+
// The no-op safety path: a provider that never reports stop_reason (every non-Anthropic
|
|
671
|
+
// provider today) finishes the turn as before — no spurious continue.
|
|
672
|
+
let mut sm = sm();
|
|
673
|
+
sm.start(RuntimeTask::new("answer"));
|
|
674
|
+
let action = sm.feed(LoopEvent::LLMResponse { message: Message::assistant("the answer") });
|
|
675
|
+
assert!(matches!(action, LoopAction::Done { .. }));
|
|
676
|
+
assert_eq!(sm.output_recovery_attempts, 0);
|
|
677
|
+
}
|
|
678
|
+
|
|
540
679
|
// ---- Layer 5: AutoCompact → semantic page-out (SDK does the LLM summary) ----
|
|
541
680
|
//
|
|
542
681
|
// Contract: AutoCompact keeps a structural summary in-context (sync, zero-I/O) and pages the
|
|
@@ -14,6 +14,11 @@ pub enum TerminationReason {
|
|
|
14
14
|
Error,
|
|
15
15
|
/// Milestone phase retry budget exhausted and rollback_policy = Terminate.
|
|
16
16
|
MilestoneExceeded,
|
|
17
|
+
/// Reactive recovery ladder exhausted on a provider context-overflow (prompt-too-long /
|
|
18
|
+
/// 413): the kernel compacted as hard as it could and the prompt still won't fit. Distinct
|
|
19
|
+
/// from `Timeout` (which the SDK used to fabricate for this case) so embedders can tell an
|
|
20
|
+
/// unrecoverable overflow from a wall-clock deadline.
|
|
21
|
+
ContextOverflow,
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
@@ -259,6 +259,9 @@ class AnthropicProvider:
|
|
|
259
259
|
# read + cache write) for accurate context accounting.
|
|
260
260
|
full_input = uncached_input + cache_read + cache_creation
|
|
261
261
|
by_slot = _estimate_cache_read_by_slot(cache_read, slot_bp)
|
|
262
|
+
# stop_reason is only present on message_delta (the closing frame);
|
|
263
|
+
# "max_tokens" drives the kernel's output-cap recovery.
|
|
264
|
+
stop_reason = getattr(getattr(event, "delta", None), "stop_reason", None)
|
|
262
265
|
yield UsageEvent(
|
|
263
266
|
total_tokens=full_input + output_tokens,
|
|
264
267
|
input_tokens=full_input,
|
|
@@ -266,6 +269,7 @@ class AnthropicProvider:
|
|
|
266
269
|
cache_read_input_tokens=cache_read,
|
|
267
270
|
cache_creation_input_tokens=cache_creation,
|
|
268
271
|
cache_read_input_tokens_by_slot=by_slot,
|
|
272
|
+
stop_reason=stop_reason,
|
|
269
273
|
)
|
|
270
274
|
elif event.type == "content_block_start":
|
|
271
275
|
idx = event.index
|
|
@@ -304,6 +304,10 @@ class OpenAIProvider(ReasoningReplayMixin):
|
|
|
304
304
|
stream_options={"include_usage": True},
|
|
305
305
|
)
|
|
306
306
|
|
|
307
|
+
# Phase 4: OpenAI signals an output-cap truncation via finish_reason="length", which arrives
|
|
308
|
+
# on a choices frame BEFORE the trailing usage frame — capture it and attach it to the usage
|
|
309
|
+
# event. The kernel treats "length" as a truncation (== Anthropic "max_tokens").
|
|
310
|
+
finish_reason_seen: "str | None" = None
|
|
307
311
|
async for chunk in stream:
|
|
308
312
|
usage = getattr(chunk, "usage", None)
|
|
309
313
|
if usage:
|
|
@@ -314,11 +318,14 @@ class OpenAIProvider(ReasoningReplayMixin):
|
|
|
314
318
|
input_tokens=getattr(usage, "prompt_tokens", 0) or 0,
|
|
315
319
|
output_tokens=getattr(usage, "completion_tokens", 0) or 0,
|
|
316
320
|
cache_read_input_tokens=openai_cached_prompt_tokens(usage),
|
|
321
|
+
stop_reason=finish_reason_seen,
|
|
317
322
|
)
|
|
318
323
|
continue
|
|
319
324
|
choice = chunk.choices[0] if chunk.choices else None
|
|
320
325
|
if not choice:
|
|
321
326
|
continue
|
|
327
|
+
if choice.finish_reason:
|
|
328
|
+
finish_reason_seen = choice.finish_reason
|
|
322
329
|
|
|
323
330
|
delta = getattr(choice, "delta", None)
|
|
324
331
|
if not delta:
|
|
@@ -66,6 +66,9 @@ class UsageEvent:
|
|
|
66
66
|
# I1: pro-rata per-slot attribution of cache_read_input_tokens (Anthropic only). None when the
|
|
67
67
|
# provider doesn't honor cache_control. Shape: {"system": int?, "tools": int?, "messages": int?}.
|
|
68
68
|
cache_read_input_tokens_by_slot: "dict | None" = None
|
|
69
|
+
# Provider stop reason — "max_tokens" (Anthropic) / "length" (OpenAI) flag an output-cap
|
|
70
|
+
# truncation that drives the kernel's max-output-tokens recovery. None when not reported.
|
|
71
|
+
stop_reason: "str | None" = None
|
|
69
72
|
|
|
70
73
|
|
|
71
74
|
@dataclass
|