memleaf 0.2.42__tar.gz → 0.2.44__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 (246) hide show
  1. {memleaf-0.2.42 → memleaf-0.2.44}/CHANGELOG.md +12 -0
  2. {memleaf-0.2.42 → memleaf-0.2.44}/MANIFEST.in +2 -3
  3. {memleaf-0.2.42/src/memleaf.egg-info → memleaf-0.2.44}/PKG-INFO +6 -12
  4. {memleaf-0.2.42 → memleaf-0.2.44}/README.en.md +5 -11
  5. {memleaf-0.2.42 → memleaf-0.2.44}/README.md +5 -11
  6. memleaf-0.2.44/docs/extraction-latency.md +50 -0
  7. {memleaf-0.2.42 → memleaf-0.2.44}/docs/general-processing.md +25 -18
  8. {memleaf-0.2.42 → memleaf-0.2.44}/docs/processing-quality-acceptance.md +10 -2
  9. memleaf-0.2.44/examples/README.md +19 -0
  10. {memleaf-0.2.42 → memleaf-0.2.44}/pyproject.toml +1 -1
  11. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/__init__.py +1 -1
  12. memleaf-0.2.44/src/memleaf/extraction_budget.py +199 -0
  13. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/extraction_capability.py +6 -7
  14. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/extraction_work_state.py +9 -66
  15. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/hermes_provider/plugin.yaml +1 -1
  16. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/process_jobs.py +13 -0
  17. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/processing.py +15 -26
  18. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/single_pass_plan.py +3 -4
  19. {memleaf-0.2.42 → memleaf-0.2.44/src/memleaf.egg-info}/PKG-INFO +6 -12
  20. memleaf-0.2.44/src/memleaf.egg-info/SOURCES.txt +110 -0
  21. memleaf-0.2.42/IMPLEMENTATION_PLAN.md +0 -143
  22. memleaf-0.2.42/RELEASE_CHECKLIST.md +0 -97
  23. memleaf-0.2.42/examples/README.md +0 -35
  24. memleaf-0.2.42/examples/live_core_lifecycle_acceptance.py +0 -156
  25. memleaf-0.2.42/examples/live_processing_acceptance.py +0 -126
  26. memleaf-0.2.42/src/memleaf/extraction_budget.py +0 -280
  27. memleaf-0.2.42/src/memleaf.egg-info/SOURCES.txt +0 -240
  28. memleaf-0.2.42/tests/__init__.py +0 -0
  29. memleaf-0.2.42/tests/hermes_provider_support.py +0 -189
  30. memleaf-0.2.42/tests/semantic_fixtures.py +0 -115
  31. memleaf-0.2.42/tests/stage_b1_support.py +0 -110
  32. memleaf-0.2.42/tests/stage_b2a_support.py +0 -139
  33. memleaf-0.2.42/tests/test_admission_noise.py +0 -1169
  34. memleaf-0.2.42/tests/test_audit_followup_v041.py +0 -496
  35. memleaf-0.2.42/tests/test_automatic_duplicate_noop_collision.py +0 -126
  36. memleaf-0.2.42/tests/test_b3_activation.py +0 -131
  37. memleaf-0.2.42/tests/test_b3_closeout.py +0 -302
  38. memleaf-0.2.42/tests/test_b3_planning_context.py +0 -74
  39. memleaf-0.2.42/tests/test_b3_scope_provenance.py +0 -187
  40. memleaf-0.2.42/tests/test_b3_single_pass_memory_planner.py +0 -315
  41. memleaf-0.2.42/tests/test_b3_single_pass_plan.py +0 -461
  42. memleaf-0.2.42/tests/test_backlog_bound_v2.py +0 -81
  43. memleaf-0.2.42/tests/test_batch_review.py +0 -215
  44. memleaf-0.2.42/tests/test_batch_review_integration.py +0 -146
  45. memleaf-0.2.42/tests/test_candidate_polarity.py +0 -220
  46. memleaf-0.2.42/tests/test_codex_install.py +0 -339
  47. memleaf-0.2.42/tests/test_codex_native_cli.py +0 -90
  48. memleaf-0.2.42/tests/test_config_migrations_v028.py +0 -60
  49. memleaf-0.2.42/tests/test_context_budget.py +0 -259
  50. memleaf-0.2.42/tests/test_conversation_only.py +0 -111
  51. memleaf-0.2.42/tests/test_credential_safety.py +0 -98
  52. memleaf-0.2.42/tests/test_cross_host_acceptance.py +0 -143
  53. memleaf-0.2.42/tests/test_cross_turn_dedupe_regressions.py +0 -194
  54. memleaf-0.2.42/tests/test_due_date_grounding.py +0 -123
  55. memleaf-0.2.42/tests/test_due_date_grounding_retry.py +0 -136
  56. memleaf-0.2.42/tests/test_email_actionable_coverage.py +0 -407
  57. memleaf-0.2.42/tests/test_evidence_budget.py +0 -192
  58. memleaf-0.2.42/tests/test_evidence_retention_policy.py +0 -450
  59. memleaf-0.2.42/tests/test_explicit_remember_budget_v2.py +0 -91
  60. memleaf-0.2.42/tests/test_external_source_dates.py +0 -259
  61. memleaf-0.2.42/tests/test_extraction_quality_regressions.py +0 -752
  62. memleaf-0.2.42/tests/test_extraction_unified_v2.py +0 -244
  63. memleaf-0.2.42/tests/test_gate_capacity.py +0 -588
  64. memleaf-0.2.42/tests/test_gate_output_protocol_v041.py +0 -222
  65. memleaf-0.2.42/tests/test_gate_output_references_v041.py +0 -49
  66. memleaf-0.2.42/tests/test_gate_protocol_matrix_v041.py +0 -112
  67. memleaf-0.2.42/tests/test_gate_schema_repair.py +0 -200
  68. memleaf-0.2.42/tests/test_gate_scope_latency_v038.py +0 -204
  69. memleaf-0.2.42/tests/test_general_evidence_admission.py +0 -874
  70. memleaf-0.2.42/tests/test_general_tool_provenance.py +0 -62
  71. memleaf-0.2.42/tests/test_global_todo_acceptance.py +0 -291
  72. memleaf-0.2.42/tests/test_global_todo_query_no_write.py +0 -459
  73. memleaf-0.2.42/tests/test_global_todo_retrieval.py +0 -112
  74. memleaf-0.2.42/tests/test_hermes_native_registration.py +0 -144
  75. memleaf-0.2.42/tests/test_hermes_provider.py +0 -9
  76. memleaf-0.2.42/tests/test_hermes_provider_01.py +0 -624
  77. memleaf-0.2.42/tests/test_hermes_provider_02.py +0 -705
  78. memleaf-0.2.42/tests/test_hermes_provider_03.py +0 -663
  79. memleaf-0.2.42/tests/test_hermes_provider_04.py +0 -331
  80. memleaf-0.2.42/tests/test_hermes_runtime_install.py +0 -674
  81. memleaf-0.2.42/tests/test_hermes_stdio_transport.py +0 -129
  82. memleaf-0.2.42/tests/test_hermes_transport_evidence.py +0 -92
  83. memleaf-0.2.42/tests/test_hermes_windows_subprocess.py +0 -43
  84. memleaf-0.2.42/tests/test_host_events.py +0 -839
  85. memleaf-0.2.42/tests/test_host_runtime_contract.py +0 -111
  86. memleaf-0.2.42/tests/test_inspection_state_v028.py +0 -33
  87. memleaf-0.2.42/tests/test_install.py +0 -431
  88. memleaf-0.2.42/tests/test_long_run_hygiene.py +0 -179
  89. memleaf-0.2.42/tests/test_maintenance_v2.py +0 -1414
  90. memleaf-0.2.42/tests/test_model_discovery.py +0 -296
  91. memleaf-0.2.42/tests/test_model_invalid_response_metrics_v041.py +0 -87
  92. memleaf-0.2.42/tests/test_model_owned_fields.py +0 -255
  93. memleaf-0.2.42/tests/test_new_scope_source_grounding.py +0 -234
  94. memleaf-0.2.42/tests/test_p3_summary_batch.py +0 -233
  95. memleaf-0.2.42/tests/test_partial_retry_idempotency.py +0 -330
  96. memleaf-0.2.42/tests/test_phase2_model_decisions.py +0 -337
  97. memleaf-0.2.42/tests/test_process_jobs.py +0 -151
  98. memleaf-0.2.42/tests/test_process_jobs_backlog_v2.py +0 -51
  99. memleaf-0.2.42/tests/test_process_jobs_state_corruption_v2.py +0 -107
  100. memleaf-0.2.42/tests/test_process_owner_locking.py +0 -96
  101. memleaf-0.2.42/tests/test_processing_contract_v026.py +0 -329
  102. memleaf-0.2.42/tests/test_processing_observability_concurrency.py +0 -362
  103. memleaf-0.2.42/tests/test_prompt_role_slim_v040.py +0 -106
  104. memleaf-0.2.42/tests/test_provider_neutral_thinking_v039.py +0 -246
  105. memleaf-0.2.42/tests/test_pypi_install.py +0 -284
  106. memleaf-0.2.42/tests/test_read_only_deferred_isolation.py +0 -194
  107. memleaf-0.2.42/tests/test_read_only_zero_call_v2.py +0 -116
  108. memleaf-0.2.42/tests/test_retrieval_gate.py +0 -183
  109. memleaf-0.2.42/tests/test_retrieval_v2.py +0 -203
  110. memleaf-0.2.42/tests/test_review_source_context.py +0 -46
  111. memleaf-0.2.42/tests/test_revision_digest.py +0 -49
  112. memleaf-0.2.42/tests/test_session_lineage.py +0 -421
  113. memleaf-0.2.42/tests/test_shared_memory_refactor.py +0 -257
  114. memleaf-0.2.42/tests/test_single_pass_prompt_slim_v2.py +0 -73
  115. memleaf-0.2.42/tests/test_single_pass_protocol_capability_v2.py +0 -186
  116. memleaf-0.2.42/tests/test_source_neutral_todos_v028.py +0 -49
  117. memleaf-0.2.42/tests/test_stage_a.py +0 -430
  118. memleaf-0.2.42/tests/test_stage_b1.py +0 -1151
  119. memleaf-0.2.42/tests/test_stage_b1_01.py +0 -497
  120. memleaf-0.2.42/tests/test_stage_b2a.py +0 -9
  121. memleaf-0.2.42/tests/test_stage_b2a_01.py +0 -530
  122. memleaf-0.2.42/tests/test_stage_b2a_02.py +0 -674
  123. memleaf-0.2.42/tests/test_stage_b2a_03.py +0 -641
  124. memleaf-0.2.42/tests/test_stage_b2a_04.py +0 -629
  125. memleaf-0.2.42/tests/test_stage_b2a_05.py +0 -317
  126. memleaf-0.2.42/tests/test_stage_b2b.py +0 -609
  127. memleaf-0.2.42/tests/test_stage_b3a_commit.py +0 -443
  128. memleaf-0.2.42/tests/test_stage_b3a_contract.py +0 -283
  129. memleaf-0.2.42/tests/test_stage_b3b_native_context.py +0 -299
  130. memleaf-0.2.42/tests/test_stage_b3b_native_index.py +0 -268
  131. memleaf-0.2.42/tests/test_stage_b3b_scope.py +0 -369
  132. memleaf-0.2.42/tests/test_stage_b3c_retrieval.py +0 -308
  133. memleaf-0.2.42/tests/test_stage_b3d_scope_maintenance.py +0 -594
  134. memleaf-0.2.42/tests/test_stage_c1_mcp.py +0 -808
  135. memleaf-0.2.42/tests/test_stage_c2_init.py +0 -802
  136. memleaf-0.2.42/tests/test_stage_c3_packaging.py +0 -165
  137. memleaf-0.2.42/tests/test_state_layout_v028.py +0 -200
  138. memleaf-0.2.42/tests/test_summary_date_grounding_integration.py +0 -39
  139. memleaf-0.2.42/tests/test_target_reconciliation.py +0 -345
  140. memleaf-0.2.42/tests/test_target_reconciliation_integration.py +0 -50
  141. memleaf-0.2.42/tests/test_todo_state_recovery.py +0 -312
  142. memleaf-0.2.42/tests/test_update_review.py +0 -779
  143. memleaf-0.2.42/tests/test_update_target_recovery.py +0 -1057
  144. memleaf-0.2.42/tests/test_upgrade_preserves_vault.py +0 -108
  145. memleaf-0.2.42/tests/test_v023_scope_correction.py +0 -185
  146. memleaf-0.2.42/tests/test_v2_gate_limits.py +0 -111
  147. memleaf-0.2.42/tests/test_v2_host_flow.py +0 -240
  148. memleaf-0.2.42/tests/test_v2_mcp_flow.py +0 -481
  149. memleaf-0.2.42/tests/test_v2_nomatch_semantics.py +0 -106
  150. memleaf-0.2.42/tests/test_v2_search_gate_acceptance.py +0 -147
  151. memleaf-0.2.42/tests/test_whole_unit_bindings.py +0 -361
  152. memleaf-0.2.42/tests/test_windows_public_mcp_launcher.py +0 -75
  153. memleaf-0.2.42/tests/test_worker_owner_state_v2.py +0 -87
  154. memleaf-0.2.42/tests/test_worker_restart_budget_v2.py +0 -259
  155. {memleaf-0.2.42 → memleaf-0.2.44}/LICENSE +0 -0
  156. {memleaf-0.2.42 → memleaf-0.2.44}/docs/capture-budget-design.md +0 -0
  157. {memleaf-0.2.42 → memleaf-0.2.44}/docs/config-migrations.md +0 -0
  158. {memleaf-0.2.42 → memleaf-0.2.44}/docs/core-refactor.md +0 -0
  159. {memleaf-0.2.42 → memleaf-0.2.44}/docs/evidence-retention.md +0 -0
  160. {memleaf-0.2.42 → memleaf-0.2.44}/docs/gate-evidence-boundary.md +0 -0
  161. {memleaf-0.2.42 → memleaf-0.2.44}/docs/hermes-mcp-runtime.md +0 -0
  162. {memleaf-0.2.42 → memleaf-0.2.44}/docs/v0.2.26-processing-status.md +0 -0
  163. {memleaf-0.2.42 → memleaf-0.2.44}/examples/basic_usage.py +0 -0
  164. {memleaf-0.2.42 → memleaf-0.2.44}/examples/mcp_stdio.ndjson +0 -0
  165. {memleaf-0.2.42 → memleaf-0.2.44}/install.ps1 +0 -0
  166. {memleaf-0.2.42 → memleaf-0.2.44}/install.sh +0 -0
  167. {memleaf-0.2.42 → memleaf-0.2.44}/setup.cfg +0 -0
  168. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/__main__.py +0 -0
  169. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/adapters/__init__.py +0 -0
  170. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/adapters/antigravity.py +0 -0
  171. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/adapters/base.py +0 -0
  172. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/adapters/codex.py +0 -0
  173. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/adapters/hermes.py +0 -0
  174. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/admission.py +0 -0
  175. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/batch_review.py +0 -0
  176. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/budget.py +0 -0
  177. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/capture.py +0 -0
  178. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/cli.py +0 -0
  179. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/compaction.py +0 -0
  180. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/config.py +0 -0
  181. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/create_coordinator.py +0 -0
  182. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/credentials.py +0 -0
  183. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/evidence_budget.py +0 -0
  184. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/evidence_policy.py +0 -0
  185. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/evidence_structure.py +0 -0
  186. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/evidence_syntax.py +0 -0
  187. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/frontmatter.py +0 -0
  188. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/hermes_provider/README.md +0 -0
  189. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/hermes_provider/__init__.py +0 -0
  190. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/hermes_provider/_mcp_client.py +0 -0
  191. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/hermes_provider/_provider.py +0 -0
  192. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/hermes_provider/_shared.py +0 -0
  193. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/hermes_provider/evidence_budget.py +0 -0
  194. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/hermes_runtime.py +0 -0
  195. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/host_events.py +0 -0
  196. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/host_runtime.py +0 -0
  197. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/inbox.py +0 -0
  198. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/index.py +0 -0
  199. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/inspection.py +0 -0
  200. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/installer.py +0 -0
  201. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/llm/__init__.py +0 -0
  202. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/llm/base.py +0 -0
  203. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/llm/claude_compatible.py +0 -0
  204. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/llm/gemini.py +0 -0
  205. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/llm/openai_compatible.py +0 -0
  206. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/llm/router.py +0 -0
  207. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/llm/thinking.py +0 -0
  208. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/locking.py +0 -0
  209. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/mcp_server.py +0 -0
  210. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/memory_commit.py +0 -0
  211. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/memory_planner.py +0 -0
  212. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/memory_writer.py +0 -0
  213. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/model_discovery.py +0 -0
  214. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/model_execution.py +0 -0
  215. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/models.py +0 -0
  216. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/native_index.py +0 -0
  217. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/native_registration.py +0 -0
  218. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/parallel_model.py +0 -0
  219. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/planning_context.py +0 -0
  220. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/process_common.py +0 -0
  221. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/process_journal.py +0 -0
  222. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/process_owner.py +0 -0
  223. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/prompts.py +0 -0
  224. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/provenance.py +0 -0
  225. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/recording_policy.py +0 -0
  226. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/redaction.py +0 -0
  227. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/retention.py +0 -0
  228. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/retrieval.py +0 -0
  229. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/retrieval_gate.py +0 -0
  230. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/scope_maintenance.py +0 -0
  231. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/scope_state.py +0 -0
  232. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/service.py +0 -0
  233. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/single_pass_memory_planner.py +0 -0
  234. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/source_policy.py +0 -0
  235. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/state_layout.py +0 -0
  236. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/summary_batch.py +0 -0
  237. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/target_reconciliation.py +0 -0
  238. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/turn_audit.py +0 -0
  239. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/turn_plan.py +0 -0
  240. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/update_coordinator.py +0 -0
  241. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/update_review.py +0 -0
  242. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/validation.py +0 -0
  243. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf/vault.py +0 -0
  244. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf.egg-info/dependency_links.txt +0 -0
  245. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf.egg-info/entry_points.txt +0 -0
  246. {memleaf-0.2.42 → memleaf-0.2.44}/src/memleaf.egg-info/top_level.txt +0 -0
@@ -2,6 +2,18 @@
2
2
 
3
3
  All notable changes to memleaf are documented here.
4
4
 
5
+ ## 0.2.44 — 2026-09-12
6
+
7
+ - Correct the extraction latency policy: ten seconds is an advisory performance target, not a cancellation or commit deadline. Remove the fixed six-second primary cap and eight-second shared window; single-pass HTTP requests now honor `llm.request_timeout`, including the one bounded format repair.
8
+ - Accept valid, Core-checked results after the target and recover frozen background plans without expiring them by old wall-clock timestamps. Keep durable request counts, damaged-state handling, revision/ownership validation and normal transport errors intact.
9
+ - Add structural per-turn extraction timing to synchronous results and background job result/error summaries. Track planning/commit time, slow attempts and failed attempts separately; a fast failure is not counted as successful target achievement. No prompt, response, credential or memory body is added to these metrics.
10
+ - Keep tests local-only and leave the hosted build/smoke CI policy unchanged. Real-provider latency and quality acceptance remain separate; removing premature cancellation is not a measured ten-second performance claim.
11
+
12
+ ## 0.2.43 — 2026-09-12
13
+
14
+ - Keep regression tests, live-acceptance helpers, and implementation/release plans in local checkouts rather than the Git tree or source distribution.
15
+ - Narrow hosted CI to package build and installed-command smoke checks while retaining the existing release metadata checks and GitHub/PyPI publication workflow.
16
+
5
17
  ## 0.2.42 — 2026-09-12
6
18
 
7
19
  - Rework ordinary automatic extraction around the unified `b3-single-pass-v1` planner. The current visible user/assistant turn is the only new-fact evidence; existing local/native memories remain comparison context. One structured model stage decides CREATE / UPDATE / NO_CHANGE / DEFERRED, with deterministic Core validation and at most one bounded format/structure repair.
@@ -4,9 +4,8 @@ include install.sh
4
4
  include install.ps1
5
5
  include README.md
6
6
  include README.en.md
7
- include RELEASE_CHECKLIST.md
8
- include IMPLEMENTATION_PLAN.md
9
- recursive-include tests *.py
10
7
  recursive-include examples *.md *.ndjson *.py
8
+ recursive-exclude examples *_acceptance.py
9
+ prune tests
11
10
 
12
11
  include docs/*.md
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memleaf
3
- Version: 0.2.42
3
+ Version: 0.2.44
4
4
  Summary: A local-first Markdown memory core for AI agents
5
5
  Author: memleaf contributors
6
6
  License-Expression: MIT
@@ -23,7 +23,7 @@ Dynamic: license-file
23
23
 
24
24
  [English](README.en.md) · [PyPI](https://pypi.org/project/memleaf/) · [GitHub](https://github.com/miffyblueboo/memleaf)
25
25
 
26
- > **版本:0.2.42。**
26
+ > **版本:0.2.44。**
27
27
  > 记忆只从用户与 Agent 的可见对话提炼。Agent 已在回复中整理的事实、项目进展和明确待办可作为来源;邮件、附件、网页和终端等工具原文不进入记忆提炼。模型负责保留原话中的不确定性,现有记忆仅用于比较、去重和更新。Markdown 仍是唯一事实源。
28
28
  > **当前版本支持 Hermes 和 Codex。** Antigravity(反重力)不检测、不安装、不配置。
29
29
 
@@ -85,6 +85,8 @@ Hermes 使用原生 MemoryProvider 维护生命周期,并通过 MCP 获取 Sco
85
85
 
86
86
  ## 记忆提炼规则
87
87
 
88
+ **10 秒是自动提炼的性能目标,不是失败阈值。** HTTP 请求遵循 `llm.request_timeout`(默认 120 秒);正常返回且通过 Core 校验的结果不会仅因超过 10 秒而丢弃。模型单次请求、有限修复和逐轮提交保留,耗时与是否完成分别统计。详见 [提炼耗时与超时](docs/extraction-latency.md)。
89
+
88
90
  memleaf 不把每句话都保存为记忆。处理一轮完整的 user + assistant 可见文本时,模型先判断是否存在明确的未来复用价值:
89
91
 
90
92
  - `CREATE`:没有相关现存记忆,创建一条原子、可独立理解的记忆;
@@ -451,17 +453,9 @@ history:
451
453
  - 选择 API/云端模型后,模型处理输入会离开本机并发送给该提供方;
452
454
  - `context()` 或无宿主绑定的客户端只能获得单页边界,不能宣称具备跨轮硬预算。
453
455
 
454
- ## 开发与验证
455
-
456
- 要求 Python 3.11+。本地运行测试:
457
-
458
- ```bash
459
- PYTHONPATH=src python3.11 -m unittest discover -s tests -p 'test_*.py' -v
460
- python3.11 -m compileall -q src tests examples
461
- git diff --check
462
- ```
456
+ ## 开发与构建
463
457
 
464
- GitHub Actions 覆盖 Linux 与 Windows 的 Python 3.11、3.12、3.13 测试,并验证 wheel/source distribution 构建、源码包测试和 Windows PowerShell 安装器语法。构建发行包需要 `build`:
458
+ 要求 Python 3.11+。GitHub Actions 在 Python 3.12 上构建 wheel 和 source distribution,并对安装后的包、命令入口和版本信息做冒烟检查。构建发行包需要 `build`:
465
459
 
466
460
  ```bash
467
461
  python -m pip install build
@@ -4,7 +4,7 @@
4
4
 
5
5
  [中文](README.md) · [PyPI](https://pypi.org/project/memleaf/) · [GitHub](https://github.com/miffyblueboo/memleaf)
6
6
 
7
- > **Version: 0.2.42.**
7
+ > **Version: 0.2.44.**
8
8
  > Automatic extraction now uses only the current turn's visible user input and final assistant reply. Raw tool output, attachments, web/file/terminal payloads and legacy tool-evidence bodies are not new source evidence; existing or retrieved memory remains comparison context rather than source authority. Background processing is persisted as a local job and can be checked through the read-only `process_status` MCP tool; failed work remains retryable and fail closed. The release also tightens source/date grounding, target reconciliation, duplicate/no-op handling and semantic review before writes. Markdown remains the sole source of truth with no SQLite runtime dependency. Acceptance covers deterministic regression suites and synthetic inputs; it does not claim real-mail or customer-business acceptance.
9
9
  > **The current release supports Hermes and Codex.** Antigravity is not detected, installed, or configured.
10
10
 
@@ -64,6 +64,8 @@ Hermes uses a native MemoryProvider for lifecycle handling and obtains the Scope
64
64
 
65
65
  ## Memory admission and maintenance
66
66
 
67
+ **Ten seconds is an extraction performance target, not a failure deadline.** HTTP requests honor `llm.request_timeout` (120 seconds by default). A valid, Core-checked response is not discarded just because processing exceeds ten seconds. Single-pass planning, bounded repair and per-turn commits remain; completion and latency are measured separately. See [extraction latency and timeouts](docs/extraction-latency.md).
68
+
67
69
  memleaf does not save every sentence. When a complete visible user + assistant turn is processed, the model first evaluates whether it has concrete future reuse value:
68
70
 
69
71
  - `CREATE`: no related active memory exists, so create one atomic, self-contained memory;
@@ -434,17 +436,9 @@ Directories are normally created with mode `0700`, and files are stored as plain
434
436
  - If an API or cloud model is selected, model-processing input leaves the machine and is sent to that provider.
435
437
  - `context()` and clients without a host-bound retrieval turn only have per-page limits; they cannot claim cross-turn hard budgets.
436
438
 
437
- ## Development and verification
438
-
439
- Python 3.11+ is required. Run locally:
440
-
441
- ```bash
442
- PYTHONPATH=src python3.11 -m unittest discover -s tests -p 'test_*.py' -v
443
- python3.11 -m compileall -q src tests examples
444
- git diff --check
445
- ```
439
+ ## Development and builds
446
440
 
447
- GitHub Actions covers Python 3.11, 3.12, and 3.13 on Linux and Windows, plus wheel/source-distribution builds, source-archive tests, and PowerShell installer syntax validation. Build packages with:
441
+ Python 3.11+ is required. GitHub Actions builds the wheel and source distribution on Python 3.12, then smoke-checks the installed package, CLI entry points, and version commands. Build packages with:
448
442
 
449
443
  ```bash
450
444
  python -m pip install build
@@ -4,7 +4,7 @@
4
4
 
5
5
  [English](README.en.md) · [PyPI](https://pypi.org/project/memleaf/) · [GitHub](https://github.com/miffyblueboo/memleaf)
6
6
 
7
- > **版本:0.2.42。**
7
+ > **版本:0.2.44。**
8
8
  > 记忆只从用户与 Agent 的可见对话提炼。Agent 已在回复中整理的事实、项目进展和明确待办可作为来源;邮件、附件、网页和终端等工具原文不进入记忆提炼。模型负责保留原话中的不确定性,现有记忆仅用于比较、去重和更新。Markdown 仍是唯一事实源。
9
9
  > **当前版本支持 Hermes 和 Codex。** Antigravity(反重力)不检测、不安装、不配置。
10
10
 
@@ -66,6 +66,8 @@ Hermes 使用原生 MemoryProvider 维护生命周期,并通过 MCP 获取 Sco
66
66
 
67
67
  ## 记忆提炼规则
68
68
 
69
+ **10 秒是自动提炼的性能目标,不是失败阈值。** HTTP 请求遵循 `llm.request_timeout`(默认 120 秒);正常返回且通过 Core 校验的结果不会仅因超过 10 秒而丢弃。模型单次请求、有限修复和逐轮提交保留,耗时与是否完成分别统计。详见 [提炼耗时与超时](docs/extraction-latency.md)。
70
+
69
71
  memleaf 不把每句话都保存为记忆。处理一轮完整的 user + assistant 可见文本时,模型先判断是否存在明确的未来复用价值:
70
72
 
71
73
  - `CREATE`:没有相关现存记忆,创建一条原子、可独立理解的记忆;
@@ -432,17 +434,9 @@ history:
432
434
  - 选择 API/云端模型后,模型处理输入会离开本机并发送给该提供方;
433
435
  - `context()` 或无宿主绑定的客户端只能获得单页边界,不能宣称具备跨轮硬预算。
434
436
 
435
- ## 开发与验证
436
-
437
- 要求 Python 3.11+。本地运行测试:
438
-
439
- ```bash
440
- PYTHONPATH=src python3.11 -m unittest discover -s tests -p 'test_*.py' -v
441
- python3.11 -m compileall -q src tests examples
442
- git diff --check
443
- ```
437
+ ## 开发与构建
444
438
 
445
- GitHub Actions 覆盖 Linux 与 Windows 的 Python 3.11、3.12、3.13 测试,并验证 wheel/source distribution 构建、源码包测试和 Windows PowerShell 安装器语法。构建发行包需要 `build`:
439
+ 要求 Python 3.11+。GitHub Actions 在 Python 3.12 上构建 wheel 和 source distribution,并对安装后的包、命令入口和版本信息做冒烟检查。构建发行包需要 `build`:
446
440
 
447
441
  ```bash
448
442
  python -m pip install build
@@ -0,0 +1,50 @@
1
+ # 提炼耗时与请求超时
2
+
3
+ ## 性能目标与失败条件分开
4
+
5
+ 日常自动提炼以 10 秒内完成为性能目标,但到第 10 秒不会主动报失败,也不会丢弃已正常返回并通过 Core 校验的结果。超过目标说明本次处理偏慢,不说明记忆无效或没有需要保存的信息。
6
+
7
+ v0.2.42 / v0.2.43 的固定 6 秒首请求、8 秒准备加模型窗口、10 秒提交前拒绝属于错误的目标实现,当前修正移除了这些时间闸门。修正本身不代表真实模型的 P50/P95 已达标。
8
+
9
+ HTTP 请求使用已有配置:
10
+
11
+ ```yaml
12
+ llm:
13
+ request_timeout: 120
14
+ ```
15
+
16
+ 这个参数仍按现有规则校验(1~240 秒,可用小数),并传到 HTTP transport。single-pass 不再静默把它缩短为 6 秒。第二次结构修复请求使用相同的配置,不再只分到原来 8 秒窗口的剩余时间。请求超时是 transport 的等待限制,不是整个后台任务的端到端完成保证;host/Python callback 仍由其宿主负责请求超时与取消。
17
+
18
+ 真正的请求超时、网络故障、无效模型输出、目标版本冲突、写入失败仍然是失败或未完成,不能伪装为 `NO_CHANGE`。相应 inbox、journal 和恢复锚点保留,是否提交仍由 Core 校验决定。
19
+
20
+ ## 保留的调用与提交约束
21
+
22
+ 普通 B3 自动提炼仍以一次结构化模型调用为正常路径,格式/结构修复最多一次。固定安全路由保留实际 outbound 请求计数和持久化预占,不增加隐藏 host-to-API fallback。并未恢复 Gate → Summary → Review 多阶段主路径。
23
+
24
+ 每轮仍先读取持久状态、规划、校验、提交,再处理下一轮;一轮超过目标不会占用下一轮的“时间许可”。compaction 仍不在普通提炼的关键路径上。
25
+
26
+ 旧 `extraction_request_budget.json` 中的 `started_at_epoch` 可以兼容读取,但不再作为请求或提交的过期时间。已消耗的 `requests` 不清零,损坏的计数账本也不会被当作空文件重建。FrozenTurn 的恢复不需要重新申请模型时间窗口;现有内容摘要、版本、所有权和写入校验保持不变。
27
+
28
+ 这不是一个自动重放所有历史失败任务的迁移。旧 inbox 不会被清空;需要通过正常处理/重试入口继续处理,不能删除正确性状态来强行重新获取请求额度。
29
+
30
+ ## 只记录结构化耗时
31
+
32
+ 处理结果新增 `extraction_metrics`,后台 `process_status` 的 result/error 和尝试汇总保留同样的数字字段:
33
+
34
+ | 字段 | 含义 |
35
+ | --- | --- |
36
+ | `target_duration_ms` | 固定观测目标 10000 毫秒,不参与失败判断 |
37
+ | `turn_count` | 本次测量的 turn 尝试数量 |
38
+ | `failed_turn_count` | 抛出处理异常的 turn 尝试数量 |
39
+ | `over_target_turn_count` | 耗时超过目标的 turn 尝试数量,可能成功也可能失败 |
40
+ | `successful_within_target_count` | 无处理异常且在目标内结算的尝试数量 |
41
+ | `total_duration_ms` | 被测 turn 尝试的耗时之和 |
42
+ | `max_turn_duration_ms` | 最慢一次 turn 尝试耗时 |
43
+ | `planning_duration_ms` | 读取状态、准备上下文、模型调用和本地计划校验耗时 |
44
+ | `commit_duration_ms` | 从进入提交阶段到提交和预算清理返回的耗时 |
45
+
46
+ 每轮计时从选中该 turn 后、读取其处理状态前开始。它不包括排队、worker 启动及 `process()` 开头的整批 inbox claim 扫描;不能把该指标冒充用户可见端到端耗时。跨 worker 重启会产生新的尝试,旧进程的 wall-clock 起点不再限制新进程执行。
47
+
48
+ “无处理异常”包括合法 `NO_CHANGE` 和正常返回的部分处理;语义完整性仍由既有 `coverage_status`、deferred 数量、`memories_written` 和任务状态判断。因此耗时统计不替代提炼质量验收。快速失败不计入 `successful_within_target_count`。
49
+
50
+ `model_metrics` 继续分别提供模型请求耗时、调用/修复次数和输入输出长度等信息。以上结构化统计不新增正文、prompt、response 或凭证存储。
@@ -122,11 +122,15 @@ the preview. There is deliberately no apply-preview command.
122
122
 
123
123
  ## Verification boundaries
124
124
 
125
+ The regression suite and acceptance helpers referenced here are maintained
126
+ locally and are not included in this repository or its source distribution.
127
+ Remote CI validates package construction and installed command entry points only.
128
+
125
129
  Deterministic backends test schema and filesystem behavior, not hosted-model
126
- accuracy. tests/semantic_fixtures.py adapts prescribed old Gate judgments to the
127
- quote protocol for update/history/maintenance tests. It does not run in product
128
- code and is not proof those judgments are semantically sound. New adversarial
129
- protocol tests choose their quotations explicitly. No model score or absolute
130
+ accuracy. The local-only `tests/semantic_fixtures.py` adapts prescribed old Gate
131
+ judgments to the quote protocol for update/history/maintenance tests. It does
132
+ not run in product code and is not proof those judgments are semantically sound.
133
+ New adversarial protocol tests choose their quotations explicitly. No model score or absolute
130
134
  "all languages/all scenarios" accuracy claim is made.
131
135
 
132
136
  Before release, separately inspect live Model Route behavior on held-out real
@@ -142,20 +146,23 @@ that acceptance.
142
146
  Windows processing-owner liveness is queried through a process handle with
143
147
  SYNCHRONIZE access, never by sending a signal. Shared-Vault writes use a
144
148
  native byte-range lock between processes, not only a Python thread mutex.
145
- Native child-process tests run on Linux, macOS and Windows.
146
-
147
- The full Python suite runs on all three OS families. Only the `install.sh`
148
- shell-harness class is POSIX-only; Windows retains installation, upgrade,
149
- PowerShell syntax, host lifecycle and native Codex acceptance. Test launchers
150
- use native `.cmd` wrappers on Windows. Byte-preservation assertions compare
151
- actual before/after bytes, including CRLF. POSIX mode-bit checks are conditional:
152
- Windows file privacy follows the Vault directory's inherited ACLs, and Unix
153
- `0600` must not be interpreted as proof of a Windows owner-only DACL.
154
-
155
- Real-model acceptance requires an explicit model route in repository Actions:
156
- secret `MEMLEAF_LIVE_MODEL_TOKEN` and variables `MEMLEAF_LIVE_BASE_URL` and
157
- `MEMLEAF_LIVE_MODEL`. Missing configuration is a blocked acceptance result,
158
- not a passing semantic test. Never publish based only on deterministic mocks.
149
+ The local-only Python suite is intended to run on all three OS families; remote
150
+ CI does not exercise it. Native child-process tests cover Linux, macOS and
151
+ Windows when run locally.
152
+
153
+ Only the `install.sh` shell-harness class is POSIX-only; local Windows checks
154
+ cover installation, upgrade, PowerShell syntax, host lifecycle and native Codex
155
+ acceptance. Test launchers use native `.cmd` wrappers on Windows.
156
+ Byte-preservation assertions compare actual before/after bytes, including CRLF.
157
+ POSIX mode-bit checks are conditional: Windows file privacy follows the Vault
158
+ directory's inherited ACLs, and Unix `0600` must not be interpreted as proof of a
159
+ Windows owner-only DACL.
160
+
161
+ Real-model acceptance requires an explicitly configured model route using
162
+ `MEMLEAF_LIVE_MODEL_TOKEN`, `MEMLEAF_LIVE_BASE_URL`, and `MEMLEAF_LIVE_MODEL`.
163
+ Live acceptance tooling is maintained locally and is not run by remote CI.
164
+ Missing configuration is a blocked acceptance result, not a passing semantic
165
+ test. Never publish based only on deterministic mocks.
159
166
 
160
167
  ## Capture policy
161
168
 
@@ -2,6 +2,11 @@
2
2
 
3
3
  This document defines acceptance for automatic memory extraction without treating one validation layer as proof of another.
4
4
 
5
+ The deterministic suite and acceptance helpers described below are maintained
6
+ locally and are not committed or shipped. Remote CI currently validates package
7
+ construction and installed command entry points only; the layers below are not
8
+ automatically exercised by that workflow.
9
+
5
10
  ## Invariants
6
11
 
7
12
  - Markdown under `knowledge/` remains the active-memory source of truth; `history/` remains historical state.
@@ -45,9 +50,12 @@ Required checks include:
45
50
 
46
51
  Passing this layer proves only the deterministic contracts under test.
47
52
 
48
- ### 2. Cross-platform CI and MCP/background-runtime verification
53
+ ### 2. Cross-platform local regression and MCP/background-runtime verification
49
54
 
50
- Run the repository's supported OS matrix and the process-job/MCP tests. Verify that detached worker status exposes the bounded metrics, reruns aggregate metrics safely, and no model call is made while the Vault write lock is held.
55
+ Run the maintained local-only test suite on supported OSes and the
56
+ process-job/MCP tests. Verify that detached worker status exposes the bounded
57
+ metrics, reruns aggregate metrics safely, and no model call is made while the
58
+ Vault write lock is held.
51
59
 
52
60
  Passing this layer proves the tested runtime paths and platforms. It does not prove that a real model will always extract every business fact.
53
61
 
@@ -0,0 +1,19 @@
1
+ # Examples
2
+
3
+ `basic_usage.py` runs entirely offline. Without `--vault` it creates a
4
+ temporary vault; pass a directory explicitly when you want to inspect the
5
+ generated Markdown after the process exits.
6
+
7
+ The example shows a lightweight `context()` directory followed by an explicit
8
+ `read_page()` call for the selected entry. Directory results contain no body;
9
+ long bodies can be read in subsequent pages using `next_offset` and `version`.
10
+
11
+ The `mcp_stdio.ndjson` file contains one legacy initialization request and one
12
+ modern discovery request. Pipe it to the stdio adapter with an explicit vault:
13
+
14
+ ```sh
15
+ python -m memleaf.mcp_server --vault <your-vault> < examples/mcp_stdio.ndjson
16
+ ```
17
+
18
+ The NDJSON file is request-only and intentionally contains no tool call,
19
+ network endpoint, local path, or secret.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "memleaf"
7
- version = "0.2.42"
7
+ version = "0.2.44"
8
8
  description = "A local-first Markdown memory core for AI agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,6 +1,6 @@
1
1
  """Local-first Markdown memory core for AI agents."""
2
2
 
3
- __version__ = "0.2.42"
3
+ __version__ = "0.2.44"
4
4
 
5
5
  from .config import DEFAULT_CONFIG, default_config, load_config, save_config
6
6
  from .frontmatter import FrontmatterError, dump_frontmatter, dump_yaml, load_yaml, parse_frontmatter
@@ -0,0 +1,199 @@
1
+ """Outbound-request accounting and advisory extraction latency metrics.
2
+
3
+ Ten seconds is a performance target, not permission to cancel useful work or
4
+ discard a valid memory. The transport owns the configured request timeout;
5
+ this module only limits request count and observes complete-turn latency.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import time
10
+ from collections.abc import Callable
11
+ from typing import Any, Iterable, Mapping
12
+
13
+ from .llm import ModelError
14
+
15
+
16
+ MAX_MODEL_REQUESTS = 2
17
+ TARGET_TOTAL_SECONDS = 10.0
18
+
19
+
20
+ class SinglePassBudgetBackend:
21
+ """Limit one logical B3 turn to two actual provider requests.
22
+
23
+ ``single_pass_safe`` routes map one complete() call to one request, without
24
+ hidden host-to-API fallback. The optional durable reservation preserves
25
+ consumed attempts across worker restarts. Neither the first request nor
26
+ its repair overrides the transport's configured ``llm.request_timeout``.
27
+ """
28
+
29
+ single_pass_safe = True
30
+
31
+ def __init__(
32
+ self,
33
+ backend: Any,
34
+ *,
35
+ reserve_request: Callable[[], int | None] | None = None,
36
+ ):
37
+ if not hasattr(backend, "complete"):
38
+ raise TypeError("single-pass backend must expose complete()")
39
+ if reserve_request is not None and not callable(reserve_request):
40
+ raise TypeError("reserve_request must be callable")
41
+ self._backend = backend
42
+ self._requests = 0
43
+ self._reserve_request = reserve_request
44
+
45
+ @property
46
+ def provider(self) -> str:
47
+ return str(getattr(self._backend, "provider", "unknown"))
48
+
49
+ @property
50
+ def model(self) -> str:
51
+ return str(getattr(self._backend, "model", "unknown"))
52
+
53
+ @property
54
+ def parallel_safe(self) -> bool:
55
+ return getattr(self._backend, "parallel_safe", False) is True
56
+
57
+ @property
58
+ def structured_batch_safe(self) -> bool:
59
+ return getattr(self._backend, "structured_batch_safe", False) is True
60
+
61
+ @property
62
+ def request_count(self) -> int:
63
+ return self._requests
64
+
65
+ def _request_ordinal(self, *, purpose: str) -> int:
66
+ if self._requests >= MAX_MODEL_REQUESTS:
67
+ raise ModelError(
68
+ "single-pass extraction budget exhausted",
69
+ code="model_timeout",
70
+ stage=purpose or "single_pass",
71
+ )
72
+ if self._reserve_request is None:
73
+ return self._requests + 1
74
+ try:
75
+ ordinal = self._reserve_request()
76
+ except Exception as error:
77
+ raise ModelError(
78
+ "single-pass extraction request budget cannot be reserved",
79
+ code="model_timeout",
80
+ stage=purpose or "single_pass",
81
+ ) from error
82
+ if type(ordinal) is not int or not 1 <= ordinal <= MAX_MODEL_REQUESTS:
83
+ raise ModelError(
84
+ "single-pass extraction budget exhausted",
85
+ code="model_timeout",
86
+ stage=purpose or "single_pass",
87
+ )
88
+ return ordinal
89
+
90
+ def complete(
91
+ self,
92
+ prompt: str,
93
+ *,
94
+ system: str = "",
95
+ purpose: str = "",
96
+ temperature: float = 0.0,
97
+ ) -> str:
98
+ self._request_ordinal(purpose=purpose)
99
+ # Count only after durable reservation succeeds. A kill from this point
100
+ # onward still consumes that attempt, without imposing a wall deadline.
101
+ self._requests += 1
102
+ return self._backend.complete(
103
+ prompt,
104
+ system=system,
105
+ purpose=purpose,
106
+ temperature=temperature,
107
+ )
108
+
109
+ def consume_call_metrics(self) -> dict[str, Any]:
110
+ consume = getattr(self._backend, "consume_call_metrics", None)
111
+ if not callable(consume):
112
+ return {}
113
+ try:
114
+ value = consume()
115
+ except Exception:
116
+ return {}
117
+ return dict(value) if isinstance(value, Mapping) else {}
118
+
119
+
120
+ class ExtractionTiming:
121
+ """Observe one processing attempt; never authorize or reject a write.
122
+
123
+ This monotonic timer includes local planning/context, model work, validation
124
+ and commit. A worker restart begins a new measured attempt; persisted wall
125
+ timestamps from older releases are not latency-based write restrictions.
126
+ """
127
+
128
+ def __init__(self, *, clock: Any = None):
129
+ self._clock = time.monotonic if clock is None else clock
130
+ self._started = self._clock()
131
+ self._commit_started: float | None = None
132
+
133
+ def begin_commit(self) -> None:
134
+ self._commit_started = self._clock()
135
+
136
+ def finish(self, *, failed: bool = False) -> dict[str, int]:
137
+ ended = self._clock()
138
+ seconds = max(0.0, ended - self._started)
139
+ commit_started = self._commit_started
140
+ planning_end = ended if commit_started is None else commit_started
141
+ return {
142
+ "target_duration_ms": int(TARGET_TOTAL_SECONDS * 1000),
143
+ "turn_count": 1,
144
+ "failed_turn_count": int(failed),
145
+ "over_target_turn_count": int(seconds > TARGET_TOTAL_SECONDS),
146
+ "successful_within_target_count": int(not failed and seconds <= TARGET_TOTAL_SECONDS),
147
+ "total_duration_ms": int(seconds * 1000),
148
+ "max_turn_duration_ms": int(seconds * 1000),
149
+ "planning_duration_ms": int(max(0.0, planning_end - self._started) * 1000),
150
+ "commit_duration_ms": int(max(0.0, ended - planning_end) * 1000),
151
+ }
152
+
153
+
154
+ def aggregate_extraction_metrics(values: Iterable[Mapping[str, Any]]) -> dict[str, int]:
155
+ """Sum allowlisted numeric metrics; never copy arbitrary content to status."""
156
+
157
+ summed = (
158
+ "turn_count", "failed_turn_count", "over_target_turn_count",
159
+ "successful_within_target_count", "total_duration_ms",
160
+ "planning_duration_ms", "commit_duration_ms",
161
+ )
162
+ result = {key: 0 for key in summed}
163
+ result["target_duration_ms"] = int(TARGET_TOTAL_SECONDS * 1000)
164
+ result["max_turn_duration_ms"] = 0
165
+ for value in values:
166
+ if not isinstance(value, Mapping):
167
+ continue
168
+ for key in (*summed, "max_turn_duration_ms"):
169
+ item = value.get(key)
170
+ if type(item) is not int or item < 0:
171
+ continue
172
+ if key == "max_turn_duration_ms":
173
+ result[key] = max(result[key], item)
174
+ else:
175
+ result[key] += item
176
+ return result
177
+
178
+
179
+ def budget_single_pass_backend(
180
+ backend: Any,
181
+ *,
182
+ reserve_request: Callable[[], int | None] | None = None,
183
+ ) -> SinglePassBudgetBackend:
184
+ if isinstance(backend, SinglePassBudgetBackend):
185
+ return backend
186
+ return SinglePassBudgetBackend(
187
+ backend,
188
+ reserve_request=reserve_request,
189
+ )
190
+
191
+
192
+ __all__ = [
193
+ "MAX_MODEL_REQUESTS",
194
+ "TARGET_TOTAL_SECONDS",
195
+ "ExtractionTiming",
196
+ "aggregate_extraction_metrics",
197
+ "SinglePassBudgetBackend",
198
+ "budget_single_pass_backend",
199
+ ]
@@ -1,10 +1,9 @@
1
1
  """Capability checks for unified automatic extraction.
2
2
 
3
- Protocol compatibility and hard latency safety are intentionally separate.
4
- A backend may understand the B3 single-pass contract without being able to
5
- honor transport-level cancellation/deadlines. Automatic extraction can use
6
- one semantic protocol in both cases, while Processor applies the strict
7
- 8/10-second budget only to ``single_pass_safe`` transports.
3
+ Protocol compatibility and transport request accounting are separate.
4
+ A backend may understand the B3 contract without exposing a bounded transport.
5
+ ``single_pass_safe`` identifies routes suitable for outbound-request counting;
6
+ HTTP requests use the configured timeout, not the advisory ten-second target.
8
7
  """
9
8
  from __future__ import annotations
10
9
 
@@ -44,14 +43,14 @@ def _direct_protocol_capable(backend: Any) -> bool:
44
43
  return override
45
44
  # Raw Python callbacks are adapted by ModelExecutor into
46
45
  # CallableBackend. They can consume memleaf's B3 prompt, but are not
47
- # strict-deadline safe because caller-owned code may ignore
46
+ # transport-timeout safe because caller-owned code may ignore
48
47
  # timeout/cancellation entirely.
49
48
  return True
50
49
  return False
51
50
 
52
51
 
53
52
  def supports_single_pass_protocol(backend: Any) -> bool:
54
- """Return B3 protocol capability without claiming strict transport SLA."""
53
+ """Return B3 protocol capability without claiming transport guarantees."""
55
54
 
56
55
  if _direct_protocol_capable(backend):
57
56
  return True