generic-ml-cache-core 0.3.0__tar.gz → 0.4.0__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 (148) hide show
  1. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/PKG-INFO +1 -1
  2. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/pyproject.toml +1 -1
  3. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/persistence/in_memory_execution_repository.py +18 -0
  4. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/persistence/sqlite_execution_repository.py +34 -2
  5. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/execution/artifact.py +28 -1
  6. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/execution/ml_execution.py +5 -0
  7. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/run/client_run_request.py +1 -1
  8. generic_ml_cache_core-0.4.0/src/generic_ml_cache_core/application/domain/model/run/persistence_depth.py +36 -0
  9. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/inbound/run_api_execution_command.py +8 -7
  10. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/inbound/run_managed_local_execution_command.py +6 -5
  11. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/inbound/run_passthrough_execution_command.py +6 -5
  12. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/out/execution_repository_port.py +9 -0
  13. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/usecase/cached_ml_execution_service.py +69 -1
  14. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/usecase/journal_events.py +4 -0
  15. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/usecase/run_api_execution_service.py +11 -1
  16. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/usecase/run_managed_local_execution_service.py +17 -1
  17. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/usecase/run_passthrough_execution_service.py +11 -1
  18. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_client_run_request.py +1 -1
  19. generic_ml_cache_core-0.4.0/tests/test_persistence_depth.py +26 -0
  20. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_probe_command.py +1 -1
  21. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_run_api_execution_command.py +5 -2
  22. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_run_api_execution_service.py +33 -2
  23. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_run_managed_local_execution_command.py +9 -4
  24. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_run_managed_local_execution_service.py +125 -5
  25. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_run_passthrough_execution_command.py +4 -3
  26. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_run_passthrough_execution_service.py +24 -3
  27. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_sqlite_execution_repository.py +61 -0
  28. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/.gitignore +0 -0
  29. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/LICENSE +0 -0
  30. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/NOTICE +0 -0
  31. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/README.md +0 -0
  32. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/__init__.py +0 -0
  33. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/__init__.py +0 -0
  34. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/inbound/__init__.py +0 -0
  35. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/inbound/composition.py +0 -0
  36. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/__init__.py +0 -0
  37. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/api/__init__.py +0 -0
  38. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/api/stub_api_client_adapter.py +0 -0
  39. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/client/__init__.py +0 -0
  40. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/client/claude.py +0 -0
  41. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/client/codex.py +0 -0
  42. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/client/cursor.py +0 -0
  43. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/client/discover.py +0 -0
  44. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/client/isolation.py +0 -0
  45. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/client/local_client_runner.py +0 -0
  46. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/client/passthrough_client_runner.py +0 -0
  47. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/client/prime_directive.py +0 -0
  48. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/client/registry.py +0 -0
  49. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/clock/__init__.py +0 -0
  50. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/clock/system_clock.py +0 -0
  51. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/fingerprint/__init__.py +0 -0
  52. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/fingerprint/filesystem_file_fingerprint.py +0 -0
  53. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/metrics/__init__.py +0 -0
  54. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/metrics/access_registry.py +0 -0
  55. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/metrics/journal_metrics.py +0 -0
  56. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/persistence/__init__.py +0 -0
  57. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/persistence/call_identity_serialization.py +0 -0
  58. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/storage/__init__.py +0 -0
  59. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/storage/filesystem_blob_store.py +0 -0
  60. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/__init__.py +0 -0
  61. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/__init__.py +0 -0
  62. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/__init__.py +0 -0
  63. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/client_status.py +0 -0
  64. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/execution/__init__.py +0 -0
  65. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/execution/execution_failure.py +0 -0
  66. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/execution/execution_kind.py +0 -0
  67. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/execution/execution_state.py +0 -0
  68. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/identity/__init__.py +0 -0
  69. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/identity/api_call_identity.py +0 -0
  70. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/identity/call_identity.py +0 -0
  71. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/identity/managed_call_identity.py +0 -0
  72. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/identity/passthrough_call_identity.py +0 -0
  73. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/model_info.py +0 -0
  74. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/model_listing.py +0 -0
  75. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/parsed_output.py +0 -0
  76. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/probe/__init__.py +0 -0
  77. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/probe/probe_report.py +0 -0
  78. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/probe/probe_status.py +0 -0
  79. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/run/__init__.py +0 -0
  80. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/run/cache_mode.py +0 -0
  81. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/run/client_run_result.py +0 -0
  82. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/run/message.py +0 -0
  83. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/usage/__init__.py +0 -0
  84. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/usage/token_usage.py +0 -0
  85. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/usage/usage.py +0 -0
  86. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/service/__init__.py +0 -0
  87. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/service/cacheability.py +0 -0
  88. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/service/message_fingerprinting.py +0 -0
  89. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/__init__.py +0 -0
  90. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/inbound/__init__.py +0 -0
  91. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/inbound/probe_command.py +0 -0
  92. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/inbound/probe_use_case.py +0 -0
  93. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/inbound/run_api_execution_use_case.py +0 -0
  94. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/inbound/run_managed_local_execution_use_case.py +0 -0
  95. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/inbound/run_passthrough_execution_use_case.py +0 -0
  96. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/out/__init__.py +0 -0
  97. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/out/api_client_port.py +0 -0
  98. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/out/base.py +0 -0
  99. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/out/blob_store_port.py +0 -0
  100. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/out/client_runner_port.py +0 -0
  101. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/out/clock_port.py +0 -0
  102. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/out/file_fingerprint_port.py +0 -0
  103. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/out/metrics_port.py +0 -0
  104. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/out/passthrough_runner_port.py +0 -0
  105. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/usecase/__init__.py +0 -0
  106. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/usecase/call_identity_building.py +0 -0
  107. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/usecase/probe_service.py +0 -0
  108. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/common/__init__.py +0 -0
  109. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/common/checksum.py +0 -0
  110. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/common/errors.py +0 -0
  111. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/stream.py +0 -0
  112. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/conftest.py +0 -0
  113. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/fake_client.py +0 -0
  114. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_adapters.py +0 -0
  115. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_api_call_identity.py +0 -0
  116. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_api_client_port.py +0 -0
  117. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_artifact.py +0 -0
  118. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_blob_store_port.py +0 -0
  119. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_cache_mode.py +0 -0
  120. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_cacheability.py +0 -0
  121. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_call_identity_building.py +0 -0
  122. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_call_identity_serialization.py +0 -0
  123. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_checksum.py +0 -0
  124. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_client_run_result.py +0 -0
  125. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_client_runner_port.py +0 -0
  126. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_clock_port.py +0 -0
  127. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_composition.py +0 -0
  128. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_execution_failure.py +0 -0
  129. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_execution_kind.py +0 -0
  130. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_execution_repository.py +0 -0
  131. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_execution_state.py +0 -0
  132. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_file_content_fingerprint.py +0 -0
  133. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_file_fingerprint_port.py +0 -0
  134. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_filesystem_blob_store.py +0 -0
  135. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_journal_metrics.py +0 -0
  136. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_local_client_runner.py +0 -0
  137. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_managed_call_identity.py +0 -0
  138. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_message.py +0 -0
  139. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_message_fingerprinting.py +0 -0
  140. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_metrics_port.py +0 -0
  141. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_ml_execution.py +0 -0
  142. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_passthrough_call_identity.py +0 -0
  143. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_passthrough_client_runner.py +0 -0
  144. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_passthrough_runner_port.py +0 -0
  145. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_probe_report.py +0 -0
  146. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_probe_service.py +0 -0
  147. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_token_usage.py +0 -0
  148. {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_usage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: generic-ml-cache-core
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Hexagonal core library for generic-ml-cache: domain, use cases, ports, and the default outbound adapters (SQLite repo, blob store, local clients, API). Stateless; inject the data source. Zero runtime deps.
5
5
  Project-URL: Homepage, https://github.com/danielslobozian/generic-ml-cache
6
6
  Project-URL: Repository, https://github.com/danielslobozian/generic-ml-cache
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "generic-ml-cache-core"
7
- version = "0.3.0"
7
+ version = "0.4.0"
8
8
  description = "Hexagonal core library for generic-ml-cache: domain, use cases, ports, and the default outbound adapters (SQLite repo, blob store, local clients, API). Stateless; inject the data source. Zero runtime deps."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -7,6 +7,10 @@ from __future__ import annotations
7
7
  from dataclasses import replace
8
8
  from typing import Dict, List, Optional, Set
9
9
 
10
+ from generic_ml_cache_core.application.domain.model.execution.artifact import (
11
+ INPUT_ARTIFACT_TYPES,
12
+ Artifact,
13
+ )
10
14
  from generic_ml_cache_core.application.domain.model.execution.execution_state import ExecutionState
11
15
  from generic_ml_cache_core.application.domain.model.execution.ml_execution import MlExecution
12
16
  from generic_ml_cache_core.application.port.out.clock_port import ClockPort
@@ -63,6 +67,20 @@ class InMemoryExecutionRepository(ExecutionRepositoryPort):
63
67
  return []
64
68
  return sorted(self._tags_by_key.get(execution_key, set()))
65
69
 
70
+ def add_input_artifacts(self, execution_key: str, artifacts: List[Artifact]) -> None:
71
+ # Back-fill the input onto the key's current execution; idempotent and a
72
+ # no-op when there is none or it already carries input.
73
+ if not artifacts:
74
+ return
75
+ for execution in self._by_key.get(execution_key, []):
76
+ if not self._is_servable(execution):
77
+ continue
78
+ if any(a.artifact_type in INPUT_ARTIFACT_TYPES for a in execution.artifacts):
79
+ return
80
+ execution.artifacts.extend(replace(a, content=None) for a in artifacts)
81
+ execution.input_persisted = True
82
+ return
83
+
66
84
  @staticmethod
67
85
  def _is_servable(execution: MlExecution) -> bool:
68
86
  """A servable execution is the current cached answer: a persisted success
@@ -16,7 +16,11 @@ from generic_ml_cache_core.adapter.out.persistence.call_identity_serialization i
16
16
  deserialize_identity,
17
17
  serialize_identity,
18
18
  )
19
- from generic_ml_cache_core.application.domain.model.execution.artifact import Artifact, ArtifactType
19
+ from generic_ml_cache_core.application.domain.model.execution.artifact import (
20
+ INPUT_ARTIFACT_TYPES,
21
+ Artifact,
22
+ ArtifactType,
23
+ )
20
24
  from generic_ml_cache_core.application.domain.model.identity.call_identity import CallIdentity
21
25
  from generic_ml_cache_core.application.domain.model.execution.execution_failure import (
22
26
  ExecutionFailure,
@@ -33,6 +37,9 @@ from generic_ml_cache_core.application.port.out.execution_repository_port import
33
37
 
34
38
  _DB_NAME = "executions.sqlite3"
35
39
 
40
+ #: stored string values of the input artifact types, for the idempotency check.
41
+ _INPUT_TYPE_VALUES = tuple(t.value for t in INPUT_ARTIFACT_TYPES)
42
+
36
43
 
37
44
  @dataclass(frozen=True)
38
45
  class ExecutionSummary:
@@ -335,6 +342,28 @@ class SqliteExecutionRepository(ExecutionRepositoryPort):
335
342
  finally:
336
343
  connection.close()
337
344
 
345
+ def add_input_artifacts(self, execution_key: str, artifacts: List[Artifact]) -> None:
346
+ if not artifacts:
347
+ return
348
+ connection = self._connect()
349
+ try:
350
+ execution_id = self._current_execution_id(connection, execution_key)
351
+ if execution_id is None:
352
+ return
353
+ # Idempotent: skip if this execution already carries input artifacts.
354
+ placeholders = ",".join("?" * len(_INPUT_TYPE_VALUES))
355
+ already = connection.execute(
356
+ f"SELECT 1 FROM artifacts WHERE execution_id = ? "
357
+ f"AND artifact_type IN ({placeholders}) LIMIT 1",
358
+ (execution_id, *_INPUT_TYPE_VALUES),
359
+ ).fetchone()
360
+ if already is not None:
361
+ return
362
+ self._insert_artifacts(connection, execution_id, artifacts)
363
+ connection.commit()
364
+ finally:
365
+ connection.close()
366
+
338
367
  # -- reconstruction ---------------------------------------------------
339
368
 
340
369
  def _load_execution(self, connection: sqlite3.Connection, row: tuple) -> MlExecution:
@@ -349,12 +378,15 @@ class SqliteExecutionRepository(ExecutionRepositoryPort):
349
378
  failure_message,
350
379
  failure_exit_code,
351
380
  ) = row
381
+ artifacts = self._load_artifacts(connection, execution_id)
352
382
  return MlExecution(
353
383
  call_identity=self._load_identity(connection, execution_key),
354
384
  execution_state=ExecutionState(state),
355
385
  execution_kind=ExecutionKind(kind),
356
386
  output_persisted=bool(output_persisted),
357
- artifacts=self._load_artifacts(connection, execution_id),
387
+ # Derived, not a column: input is persisted iff INPUT_* artifacts exist.
388
+ input_persisted=any(a.artifact_type in INPUT_ARTIFACT_TYPES for a in artifacts),
389
+ artifacts=artifacts,
358
390
  token_usage=self._load_token_usage(connection, execution_id),
359
391
  failure=(
360
392
  ExecutionFailure(
@@ -13,7 +13,15 @@ _BINARY = "binary"
13
13
 
14
14
 
15
15
  class ArtifactType(enum.Enum):
16
- """The kind of generated output an Artifact holds.
16
+ """The kind of document an Artifact holds.
17
+
18
+ The ``STDOUT``/``STDERR``/``OUTPUT_FILE`` types are an execution's *output*,
19
+ stored whenever caching is on. The ``INPUT_*`` types are the *input* sent to
20
+ the client — and are stored only at ``DATASET`` persistence depth, to build a
21
+ queryable ``(input, output)`` corpus. Each execution kind keeps its own input
22
+ shape: managed-local uses ``INPUT_CONTEXT``/``INPUT_PROMPT``/``INPUT_SYSTEM``,
23
+ the API kind a single ``INPUT_MESSAGES`` (the JSON message list), and
24
+ passthrough a single ``INPUT_ARGS`` (the JSON native-argument list).
17
25
 
18
26
  RAW_USAGE is reserved for a later step (the raw client usage block stored as
19
27
  its own artifact); today raw usage still rides on TokenUsage.
@@ -22,6 +30,11 @@ class ArtifactType(enum.Enum):
22
30
  STDOUT = "stdout"
23
31
  STDERR = "stderr"
24
32
  OUTPUT_FILE = "output_file"
33
+ INPUT_CONTEXT = "input_context"
34
+ INPUT_PROMPT = "input_prompt"
35
+ INPUT_SYSTEM = "input_system"
36
+ INPUT_MESSAGES = "input_messages"
37
+ INPUT_ARGS = "input_args"
25
38
 
26
39
 
27
40
  @dataclass(frozen=True)
@@ -76,3 +89,17 @@ class Artifact:
76
89
  def is_hydrated(self) -> bool:
77
90
  """True when the artifact's bytes are materialised in memory."""
78
91
  return self.content is not None
92
+
93
+
94
+ #: The artifact types that make up an execution's persisted *input* (DATASET
95
+ #: depth). A single place so consumers can tell input apart from output without
96
+ #: re-listing the members.
97
+ INPUT_ARTIFACT_TYPES = frozenset(
98
+ {
99
+ ArtifactType.INPUT_CONTEXT,
100
+ ArtifactType.INPUT_PROMPT,
101
+ ArtifactType.INPUT_SYSTEM,
102
+ ArtifactType.INPUT_MESSAGES,
103
+ ArtifactType.INPUT_ARGS,
104
+ }
105
+ )
@@ -29,12 +29,17 @@ class MlExecution:
29
29
  cache-currency axis (None = current, set = stale); executions are append-only
30
30
  per call identity. ``artifacts`` may be dehydrated (refs only) or hydrated
31
31
  (bytes materialised).
32
+
33
+ ``output_persisted`` is set whenever caching is on (CACHE/DATASET);
34
+ ``input_persisted`` is set only at DATASET depth, when the input is also kept
35
+ (as ``INPUT_*`` artifacts) to form a ``(input, output)`` corpus.
32
36
  """
33
37
 
34
38
  call_identity: CallIdentity
35
39
  execution_state: ExecutionState
36
40
  execution_kind: ExecutionKind
37
41
  output_persisted: bool
42
+ input_persisted: bool = False
38
43
  artifacts: List[Artifact] = field(default_factory=list)
39
44
  token_usage: Optional[TokenUsage] = None
40
45
  failure: Optional[ExecutionFailure] = None
@@ -13,7 +13,7 @@ class ClientRunRequest:
13
13
  """The DTO the use case constructs and passes to ClientRunnerPort.
14
14
 
15
15
  Carries only what the client runner needs to launch the client. The
16
- command's gmlcache-specific policy fields (cache_mode, persist_output,
16
+ command's gmlcache-specific policy fields (cache_mode, persistence_depth,
17
17
  scan_trust) do not appear here — they are the use case's concern, not
18
18
  the client runner's.
19
19
 
@@ -0,0 +1,36 @@
1
+ # SPDX-FileCopyrightText: 2026 Daniel Slobozian
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """PersistenceDepth."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from enum import Enum
8
+
9
+
10
+ class PersistenceDepth(Enum):
11
+ """How much of an execution is kept on disk — a single ordered choice.
12
+
13
+ Each level is a superset of the one below, so the degenerate "input stored
14
+ without output" state is unrepresentable:
15
+
16
+ - ``METER`` -- metadata/usage only. The call runs and is recorded, but no
17
+ output is stored, so it is never replayed (a usage/observability mode).
18
+ - ``CACHE`` -- ``METER`` plus the output: stored and replayed on a hit. The
19
+ default, and today's behaviour.
20
+ - ``DATASET`` -- ``CACHE`` plus the input: replayed and retained as a labelled
21
+ ``(input, output)`` pair.
22
+ """
23
+
24
+ METER = "meter"
25
+ CACHE = "cache"
26
+ DATASET = "dataset"
27
+
28
+ @property
29
+ def stores_output(self) -> bool:
30
+ """Whether this depth keeps the output (``CACHE`` and ``DATASET``)."""
31
+ return self in (PersistenceDepth.CACHE, PersistenceDepth.DATASET)
32
+
33
+ @property
34
+ def stores_input(self) -> bool:
35
+ """Whether this depth keeps the input (``DATASET`` only)."""
36
+ return self is PersistenceDepth.DATASET
@@ -8,6 +8,7 @@ from dataclasses import dataclass, field
8
8
  from typing import List
9
9
 
10
10
  from generic_ml_cache_core.application.domain.model.run.cache_mode import CacheMode
11
+ from generic_ml_cache_core.application.domain.model.run.persistence_depth import PersistenceDepth
11
12
  from generic_ml_cache_core.application.domain.model.run.message import Message
12
13
 
13
14
 
@@ -19,8 +20,8 @@ class RunApiExecutionCommand:
19
20
  files or scan folders), so there are no input-file, allow-path, grant, or
20
21
  scan-trust fields. An API call is always cacheable.
21
22
 
22
- Note (future): ``persist_output = False`` will be incompatible with async
23
- execution — an async call must store its output so the caller can retrieve it
23
+ Note (future): the ``METER`` depth (storing no output) will be incompatible with
24
+ async execution — an async call must store its output so the caller can retrieve it
24
25
  by id later. Async is not built yet, so nothing enforces it here.
25
26
  """
26
27
 
@@ -28,13 +29,13 @@ class RunApiExecutionCommand:
28
29
  model: str
29
30
  messages: List[Message] = field(default_factory=list)
30
31
  cache_mode: CacheMode = CacheMode.CACHE
31
- persist_output: bool = True
32
+ persistence_depth: PersistenceDepth = PersistenceDepth.CACHE
32
33
  record_on_error: bool = False
33
34
 
34
35
  def should_persist(self, succeeded: bool) -> bool:
35
- """Whether this command's policy stores an output for a run that ended
36
- with ``succeeded``: never without ``persist_output``; a failure only with
37
- ``record_on_error``."""
38
- if not self.persist_output:
36
+ """Whether this command's policy stores the output for a run that ended
37
+ with ``succeeded``: never below ``CACHE`` depth (``METER`` stores nothing);
38
+ a failure only with ``record_on_error``."""
39
+ if not self.persistence_depth.stores_output:
39
40
  return False
40
41
  return succeeded or self.record_on_error
@@ -8,6 +8,7 @@ from dataclasses import dataclass, field
8
8
  from typing import List, Optional
9
9
 
10
10
  from generic_ml_cache_core.application.domain.model.run.cache_mode import CacheMode
11
+ from generic_ml_cache_core.application.domain.model.run.persistence_depth import PersistenceDepth
11
12
  from generic_ml_cache_core.application.domain.service.cacheability import is_call_uncacheable
12
13
 
13
14
 
@@ -32,7 +33,7 @@ class RunManagedLocalExecutionCommand:
32
33
  client_args: List[str] = field(default_factory=list)
33
34
  grants: List[str] = field(default_factory=list)
34
35
  cache_mode: CacheMode = CacheMode.CACHE
35
- persist_output: bool = True
36
+ persistence_depth: PersistenceDepth = PersistenceDepth.CACHE
36
37
  record_on_error: bool = False
37
38
  tags: List[str] = field(default_factory=list)
38
39
 
@@ -41,9 +42,9 @@ class RunManagedLocalExecutionCommand:
41
42
  return is_call_uncacheable(self.allow_paths, self.scan_trust)
42
43
 
43
44
  def should_persist(self, succeeded: bool) -> bool:
44
- """Whether this command's policy stores an output for a run that ended
45
- with ``succeeded``: never without ``persist_output``; a failure only with
46
- ``record_on_error``."""
47
- if not self.persist_output:
45
+ """Whether this command's policy stores the output for a run that ended
46
+ with ``succeeded``: never below ``CACHE`` depth (``METER`` stores nothing);
47
+ a failure only with ``record_on_error``."""
48
+ if not self.persistence_depth.stores_output:
48
49
  return False
49
50
  return succeeded or self.record_on_error
@@ -8,6 +8,7 @@ from dataclasses import dataclass, field
8
8
  from typing import List
9
9
 
10
10
  from generic_ml_cache_core.application.domain.model.run.cache_mode import CacheMode
11
+ from generic_ml_cache_core.application.domain.model.run.persistence_depth import PersistenceDepth
11
12
 
12
13
 
13
14
  @dataclass(frozen=True)
@@ -23,13 +24,13 @@ class RunPassthroughExecutionCommand:
23
24
  client: str
24
25
  native_args: List[str] = field(default_factory=list)
25
26
  cache_mode: CacheMode = CacheMode.CACHE
26
- persist_output: bool = True
27
+ persistence_depth: PersistenceDepth = PersistenceDepth.CACHE
27
28
  record_on_error: bool = False
28
29
 
29
30
  def should_persist(self, succeeded: bool) -> bool:
30
- """Whether this command's policy stores an output for a run that ended
31
- with ``succeeded``: never without ``persist_output``; a failure only with
32
- ``record_on_error``."""
33
- if not self.persist_output:
31
+ """Whether this command's policy stores the output for a run that ended
32
+ with ``succeeded``: never below ``CACHE`` depth (``METER`` stores nothing);
33
+ a failure only with ``record_on_error``."""
34
+ if not self.persistence_depth.stores_output:
34
35
  return False
35
36
  return succeeded or self.record_on_error
@@ -7,6 +7,7 @@ from __future__ import annotations
7
7
  from abc import ABC, abstractmethod
8
8
  from typing import List, Optional
9
9
 
10
+ from generic_ml_cache_core.application.domain.model.execution.artifact import Artifact
10
11
  from generic_ml_cache_core.application.domain.model.execution.ml_execution import MlExecution
11
12
 
12
13
 
@@ -50,3 +51,11 @@ class ExecutionRepositoryPort(ABC):
50
51
  def tags_for(self, execution_key: str) -> List[str]:
51
52
  """Return the tags on the current execution for ``execution_key``, sorted;
52
53
  empty if none (or no current execution)."""
54
+
55
+ @abstractmethod
56
+ def add_input_artifacts(self, execution_key: str, artifacts: List[Artifact]) -> None:
57
+ """Attach input ``artifacts`` to the current execution for ``execution_key``,
58
+ back-filling the input side of the corpus when a DATASET-depth call hits an
59
+ entry that has none yet. Idempotent — a no-op if the current execution
60
+ already carries input, or if there is no current execution. Like tags, this
61
+ enriches an existing entry without rewriting its output."""
@@ -10,6 +10,7 @@ from typing import List, Optional, Protocol, Tuple
10
10
 
11
11
  from generic_ml_cache_core.application.domain.model.execution.artifact import Artifact, ArtifactType
12
12
  from generic_ml_cache_core.application.domain.model.run.cache_mode import CacheMode
13
+ from generic_ml_cache_core.application.domain.model.run.persistence_depth import PersistenceDepth
13
14
  from generic_ml_cache_core.application.domain.model.identity.call_identity import CallIdentity
14
15
  from generic_ml_cache_core.application.domain.model.run.client_run_result import ClientRunResult
15
16
  from generic_ml_cache_core.application.domain.model.execution.execution_kind import ExecutionKind
@@ -31,6 +32,7 @@ class CacheableExecutionCommand(Protocol):
31
32
  persistence policy. The kind-specific fields are read through hooks."""
32
33
 
33
34
  cache_mode: CacheMode
35
+ persistence_depth: PersistenceDepth
34
36
 
35
37
  def should_persist(self, succeeded: bool) -> bool: ...
36
38
 
@@ -63,6 +65,11 @@ class CachedMlExecutionService(ABC):
63
65
  if command.cache_mode is CacheMode.OFFLINE:
64
66
  return self._serve_offline(command, execution_key)
65
67
 
68
+ if not command.persistence_depth.stores_output:
69
+ # METER: never replays — always run, store nothing, but record whether
70
+ # the call *would* have hit a stored entry (would-be hit/miss).
71
+ return self._run_metered(command, call_identity, execution_key)
72
+
66
73
  if command.cache_mode is CacheMode.CACHE:
67
74
  current_execution = self._repository.find_current(execution_key)
68
75
  if current_execution is not None:
@@ -121,8 +128,22 @@ class CachedMlExecutionService(ABC):
121
128
  hydrated_execution = self._hydrate(current_execution)
122
129
  self._record_event(journal_events.HIT, execution_key, command)
123
130
  self._apply_tags(execution_key, command)
131
+ self._accumulate_input(command, execution_key, current_execution)
124
132
  return hydrated_execution
125
133
 
134
+ def _accumulate_input(
135
+ self, command: CacheableExecutionCommand, execution_key: str, current_execution: MlExecution
136
+ ) -> None:
137
+ """If the user now wants the input kept (DATASET) and this entry doesn't yet
138
+ carry it, back-fill it onto the existing entry — the input is in the command,
139
+ so no re-run is needed. Mirrors how tags accumulate on a hit; the user
140
+ changing their mind to enrich the stored data is their decision."""
141
+ if not command.persistence_depth.stores_input or current_execution.input_persisted:
142
+ return
143
+ input_artifacts = self._build_input_artifacts(command, store=True)
144
+ if input_artifacts:
145
+ self._repository.add_input_artifacts(execution_key, input_artifacts)
146
+
126
147
  def _run_uncacheable(
127
148
  self, command: CacheableExecutionCommand, call_identity: CallIdentity, execution_key: str
128
149
  ) -> MlExecution:
@@ -141,12 +162,17 @@ class CachedMlExecutionService(ABC):
141
162
  client_run_result = self._run_client(command)
142
163
  should_store = allow_store and command.should_persist(client_run_result.succeeded)
143
164
  artifacts = self._build_artifacts(client_run_result, store=should_store)
165
+ # Input rides on a stored output (DATASET is a superset of CACHE): only
166
+ # capture it when the output is being stored and the depth keeps input.
167
+ store_input = should_store and command.persistence_depth.stores_input
168
+ input_artifacts = self._build_input_artifacts(command, store=store_input)
144
169
  execution = MlExecution(
145
170
  call_identity=call_identity,
146
171
  execution_state=client_run_result.outcome(),
147
172
  execution_kind=self._execution_kind(),
148
173
  output_persisted=should_store,
149
- artifacts=artifacts,
174
+ input_persisted=bool(input_artifacts),
175
+ artifacts=artifacts + input_artifacts,
150
176
  token_usage=client_run_result.token_usage,
151
177
  failure=client_run_result.failure(),
152
178
  )
@@ -158,6 +184,28 @@ class CachedMlExecutionService(ABC):
158
184
  self._record_event(journal_events.RUN, execution_key, command)
159
185
  return execution
160
186
 
187
+ def _run_metered(
188
+ self, command: CacheableExecutionCommand, call_identity: CallIdentity, execution_key: str
189
+ ) -> MlExecution:
190
+ """METER depth: always run and store nothing, but journal whether a stored
191
+ entry existed — so usage analytics can report would-be hit/miss ("you'd
192
+ have saved N runs") without the cache ever serving or storing anything."""
193
+ would_hit = self._repository.find_current(execution_key) is not None
194
+ client_run_result = self._run_client(command)
195
+ execution = MlExecution(
196
+ call_identity=call_identity,
197
+ execution_state=client_run_result.outcome(),
198
+ execution_kind=self._execution_kind(),
199
+ output_persisted=False,
200
+ input_persisted=False,
201
+ artifacts=self._build_artifacts(client_run_result, store=False),
202
+ token_usage=client_run_result.token_usage,
203
+ failure=client_run_result.failure(),
204
+ )
205
+ event = journal_events.WOULD_HIT if would_hit else journal_events.WOULD_MISS
206
+ self._record_event(event, execution_key, command)
207
+ return execution
208
+
161
209
  # -- artifacts --------------------------------------------------------
162
210
 
163
211
  def _build_artifacts(self, client_run_result: ClientRunResult, store: bool) -> List[Artifact]:
@@ -189,6 +237,26 @@ class CachedMlExecutionService(ABC):
189
237
  self._blob_store.put(blob_key, content_bytes)
190
238
  return Artifact.from_content(artifact_type, blob_key, content_bytes, name=artifact_name)
191
239
 
240
+ def _build_input_artifacts(
241
+ self, command: CacheableExecutionCommand, store: bool
242
+ ) -> List[Artifact]:
243
+ """The input documents to keep at DATASET depth, content-addressed like
244
+ any artifact. Empty when ``store`` is false (below DATASET, or nothing was
245
+ stored) or when the kind has no recordable input."""
246
+ if not store:
247
+ return []
248
+ return [
249
+ self._store_artifact(artifact_type, name, content_bytes, store=True)
250
+ for (artifact_type, name, content_bytes) in self._input_parts(command)
251
+ ]
252
+
253
+ def _input_parts(
254
+ self, command: CacheableExecutionCommand
255
+ ) -> List[Tuple[ArtifactType, Optional[str], bytes]]:
256
+ """The ``(type, name, bytes)`` input documents this kind would persist at
257
+ DATASET depth. Default: none — a kind whose input is not recorded."""
258
+ return []
259
+
192
260
  def _hydrate(self, execution: MlExecution) -> MlExecution:
193
261
  hydrated_artifacts = [self._hydrate_artifact(artifact) for artifact in execution.artifacts]
194
262
  return replace(execution, artifacts=hydrated_artifacts)
@@ -17,3 +17,7 @@ RECORD = "record"
17
17
  MISS = "miss"
18
18
  #: a fresh real call ran but was not stored (uncacheable, or a non-persisted/failed run)
19
19
  RUN = "run"
20
+ #: a METER call ran (never replays) and a stored entry existed — it *would* have hit
21
+ WOULD_HIT = "would_hit"
22
+ #: a METER call ran (never replays) and no stored entry existed — it *would* have missed
23
+ WOULD_MISS = "would_miss"
@@ -4,8 +4,10 @@
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- from typing import Tuple
7
+ import json
8
+ from typing import List, Optional, Tuple
8
9
 
10
+ from generic_ml_cache_core.application.domain.model.execution.artifact import ArtifactType
9
11
  from generic_ml_cache_core.application.domain.model.identity.api_call_identity import (
10
12
  ApiCallIdentity,
11
13
  )
@@ -67,3 +69,11 @@ class RunApiExecutionService(CachedMlExecutionService, RunApiExecutionUseCase):
67
69
  def _journal_fields(self, command: RunApiExecutionCommand) -> Tuple[str, str, str]:
68
70
  # The provider plays the role of "client" in the journal; no effort concept.
69
71
  return command.provider, command.model, ""
72
+
73
+ def _input_parts(
74
+ self, command: RunApiExecutionCommand
75
+ ) -> List[Tuple[ArtifactType, Optional[str], bytes]]:
76
+ # The API call's input is its message list; keep it as one JSON artifact so
77
+ # the (role, content) structure survives into the exported corpus.
78
+ payload = json.dumps([{"role": m.role, "content": m.content} for m in command.messages])
79
+ return [(ArtifactType.INPUT_MESSAGES, None, payload.encode("utf-8"))]
@@ -4,8 +4,9 @@
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- from typing import List, Tuple
7
+ from typing import List, Optional, Tuple
8
8
 
9
+ from generic_ml_cache_core.application.domain.model.execution.artifact import ArtifactType
9
10
  from generic_ml_cache_core.application.domain.model.execution.ml_execution import normalize_tags
10
11
  from generic_ml_cache_core.application.domain.model.identity.call_identity import CallIdentity
11
12
  from generic_ml_cache_core.application.domain.model.run.client_run_request import ClientRunRequest
@@ -70,6 +71,21 @@ class RunManagedLocalExecutionService(CachedMlExecutionService, RunManagedLocalE
70
71
  def _execution_tags(self, command: RunManagedLocalExecutionCommand) -> List[str]:
71
72
  return normalize_tags(command.tags)
72
73
 
74
+ def _input_parts(
75
+ self, command: RunManagedLocalExecutionCommand
76
+ ) -> List[Tuple[ArtifactType, Optional[str], bytes]]:
77
+ # The text sent to the client, as the input side of the corpus. The prompt
78
+ # is always present; context and system prompt only when non-empty.
79
+ parts: List[Tuple[ArtifactType, Optional[str], bytes]] = []
80
+ if command.context:
81
+ parts.append((ArtifactType.INPUT_CONTEXT, None, command.context.encode("utf-8")))
82
+ parts.append((ArtifactType.INPUT_PROMPT, None, command.prompt.encode("utf-8")))
83
+ if command.user_system_prompt:
84
+ parts.append(
85
+ (ArtifactType.INPUT_SYSTEM, None, command.user_system_prompt.encode("utf-8"))
86
+ )
87
+ return parts
88
+
73
89
  @staticmethod
74
90
  def _build_client_run_request(command: RunManagedLocalExecutionCommand) -> ClientRunRequest:
75
91
  # The one allowed self-less method (AGENTS §6): the inbound-command ->
@@ -4,8 +4,10 @@
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- from typing import Tuple
7
+ import json
8
+ from typing import List, Optional, Tuple
8
9
 
10
+ from generic_ml_cache_core.application.domain.model.execution.artifact import ArtifactType
9
11
  from generic_ml_cache_core.application.domain.model.identity.call_identity import CallIdentity
10
12
  from generic_ml_cache_core.application.domain.model.run.client_run_result import ClientRunResult
11
13
  from generic_ml_cache_core.application.domain.model.execution.execution_kind import ExecutionKind
@@ -65,3 +67,11 @@ class RunPassthroughExecutionService(CachedMlExecutionService, RunPassthroughExe
65
67
  def _journal_fields(self, command: RunPassthroughExecutionCommand) -> Tuple[str, str, str]:
66
68
  # A passthrough has no modelled model/effort — only the client is known.
67
69
  return command.client, "", ""
70
+
71
+ def _input_parts(
72
+ self, command: RunPassthroughExecutionCommand
73
+ ) -> List[Tuple[ArtifactType, Optional[str], bytes]]:
74
+ # The passthrough's input is its opaque native-argument list; keep it as one
75
+ # JSON artifact so the argument vector survives into the exported corpus.
76
+ payload = json.dumps(list(command.native_args))
77
+ return [(ArtifactType.INPUT_ARGS, None, payload.encode("utf-8"))]
@@ -74,7 +74,7 @@ def test_grants_are_a_frozenset():
74
74
 
75
75
  def test_cache_policy_fields_are_absent():
76
76
  assert not hasattr(ClientRunRequest, "cache_mode")
77
- assert not hasattr(ClientRunRequest, "persist_output")
77
+ assert not hasattr(ClientRunRequest, "persistence_depth")
78
78
  assert not hasattr(ClientRunRequest, "scan_trust")
79
79
 
80
80
 
@@ -0,0 +1,26 @@
1
+ # SPDX-FileCopyrightText: 2026 Daniel Slobozian
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """Tests for PersistenceDepth."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from generic_ml_cache_core.application.domain.model.run.persistence_depth import PersistenceDepth
8
+
9
+
10
+ def test_meter_stores_neither_output_nor_input():
11
+ assert PersistenceDepth.METER.stores_output is False
12
+ assert PersistenceDepth.METER.stores_input is False
13
+
14
+
15
+ def test_cache_stores_output_but_not_input():
16
+ assert PersistenceDepth.CACHE.stores_output is True
17
+ assert PersistenceDepth.CACHE.stores_input is False
18
+
19
+
20
+ def test_dataset_stores_both_output_and_input():
21
+ assert PersistenceDepth.DATASET.stores_output is True
22
+ assert PersistenceDepth.DATASET.stores_input is True
23
+
24
+
25
+ def test_values_are_the_stable_wire_strings():
26
+ assert [depth.value for depth in PersistenceDepth] == ["meter", "cache", "dataset"]
@@ -38,7 +38,7 @@ def test_scan_trust_makes_it_cacheable_again():
38
38
 
39
39
  def test_carries_no_run_policy():
40
40
  assert not hasattr(ProbeCommand, "cache_mode")
41
- assert not hasattr(ProbeCommand, "persist_output")
41
+ assert not hasattr(ProbeCommand, "persistence_depth")
42
42
  assert not hasattr(ProbeCommand, "record_on_error")
43
43
 
44
44
 
@@ -7,6 +7,7 @@ from __future__ import annotations
7
7
  import pytest
8
8
 
9
9
  from generic_ml_cache_core.application.domain.model.run.cache_mode import CacheMode
10
+ from generic_ml_cache_core.application.domain.model.run.persistence_depth import PersistenceDepth
10
11
  from generic_ml_cache_core.application.domain.model.run.message import Message
11
12
  from generic_ml_cache_core.application.port.inbound.run_api_execution_command import (
12
13
  RunApiExecutionCommand,
@@ -17,7 +18,7 @@ def test_defaults():
17
18
  command = RunApiExecutionCommand(provider="openai", model="gpt-x")
18
19
  assert command.messages == []
19
20
  assert command.cache_mode is CacheMode.CACHE
20
- assert command.persist_output is True
21
+ assert command.persistence_depth is PersistenceDepth.CACHE
21
22
  assert command.record_on_error is False
22
23
 
23
24
 
@@ -36,7 +37,9 @@ def test_should_persist_policy():
36
37
  is True
37
38
  )
38
39
  assert (
39
- RunApiExecutionCommand(provider="p", model="m", persist_output=False).should_persist(True)
40
+ RunApiExecutionCommand(
41
+ provider="p", model="m", persistence_depth=PersistenceDepth.METER
42
+ ).should_persist(True)
40
43
  is False
41
44
  )
42
45