generic-ml-cache-core 0.2.0__py3-none-any.whl

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 (99) hide show
  1. generic_ml_cache_core/__init__.py +64 -0
  2. generic_ml_cache_core/adapter/__init__.py +1 -0
  3. generic_ml_cache_core/adapter/inbound/__init__.py +1 -0
  4. generic_ml_cache_core/adapter/inbound/composition.py +96 -0
  5. generic_ml_cache_core/adapter/out/__init__.py +1 -0
  6. generic_ml_cache_core/adapter/out/api/__init__.py +1 -0
  7. generic_ml_cache_core/adapter/out/api/stub_api_client_adapter.py +30 -0
  8. generic_ml_cache_core/adapter/out/client/__init__.py +28 -0
  9. generic_ml_cache_core/adapter/out/client/claude.py +214 -0
  10. generic_ml_cache_core/adapter/out/client/codex.py +171 -0
  11. generic_ml_cache_core/adapter/out/client/cursor.py +208 -0
  12. generic_ml_cache_core/adapter/out/client/discover.py +121 -0
  13. generic_ml_cache_core/adapter/out/client/isolation.py +396 -0
  14. generic_ml_cache_core/adapter/out/client/local_client_runner.py +54 -0
  15. generic_ml_cache_core/adapter/out/client/passthrough_client_runner.py +47 -0
  16. generic_ml_cache_core/adapter/out/client/prime_directive.py +53 -0
  17. generic_ml_cache_core/adapter/out/client/registry.py +34 -0
  18. generic_ml_cache_core/adapter/out/clock/__init__.py +1 -0
  19. generic_ml_cache_core/adapter/out/clock/system_clock.py +16 -0
  20. generic_ml_cache_core/adapter/out/fingerprint/__init__.py +1 -0
  21. generic_ml_cache_core/adapter/out/fingerprint/filesystem_file_fingerprint.py +30 -0
  22. generic_ml_cache_core/adapter/out/metrics/__init__.py +1 -0
  23. generic_ml_cache_core/adapter/out/metrics/access_registry.py +147 -0
  24. generic_ml_cache_core/adapter/out/metrics/journal_metrics.py +45 -0
  25. generic_ml_cache_core/adapter/out/persistence/__init__.py +1 -0
  26. generic_ml_cache_core/adapter/out/persistence/call_identity_serialization.py +100 -0
  27. generic_ml_cache_core/adapter/out/persistence/in_memory_execution_repository.py +69 -0
  28. generic_ml_cache_core/adapter/out/persistence/sqlite_execution_repository.py +398 -0
  29. generic_ml_cache_core/adapter/out/storage/__init__.py +1 -0
  30. generic_ml_cache_core/adapter/out/storage/filesystem_blob_store.py +47 -0
  31. generic_ml_cache_core/application/__init__.py +1 -0
  32. generic_ml_cache_core/application/domain/__init__.py +1 -0
  33. generic_ml_cache_core/application/domain/model/__init__.py +1 -0
  34. generic_ml_cache_core/application/domain/model/client_status.py +17 -0
  35. generic_ml_cache_core/application/domain/model/execution/__init__.py +1 -0
  36. generic_ml_cache_core/application/domain/model/execution/artifact.py +78 -0
  37. generic_ml_cache_core/application/domain/model/execution/execution_failure.py +32 -0
  38. generic_ml_cache_core/application/domain/model/execution/execution_kind.py +26 -0
  39. generic_ml_cache_core/application/domain/model/execution/execution_state.py +21 -0
  40. generic_ml_cache_core/application/domain/model/execution/ml_execution.py +41 -0
  41. generic_ml_cache_core/application/domain/model/identity/__init__.py +1 -0
  42. generic_ml_cache_core/application/domain/model/identity/api_call_identity.py +36 -0
  43. generic_ml_cache_core/application/domain/model/identity/call_identity.py +25 -0
  44. generic_ml_cache_core/application/domain/model/identity/managed_call_identity.py +54 -0
  45. generic_ml_cache_core/application/domain/model/identity/passthrough_call_identity.py +35 -0
  46. generic_ml_cache_core/application/domain/model/model_info.py +20 -0
  47. generic_ml_cache_core/application/domain/model/model_listing.py +29 -0
  48. generic_ml_cache_core/application/domain/model/parsed_output.py +23 -0
  49. generic_ml_cache_core/application/domain/model/probe/__init__.py +1 -0
  50. generic_ml_cache_core/application/domain/model/probe/probe_report.py +26 -0
  51. generic_ml_cache_core/application/domain/model/probe/probe_status.py +13 -0
  52. generic_ml_cache_core/application/domain/model/run/__init__.py +1 -0
  53. generic_ml_cache_core/application/domain/model/run/cache_mode.py +21 -0
  54. generic_ml_cache_core/application/domain/model/run/client_run_request.py +35 -0
  55. generic_ml_cache_core/application/domain/model/run/client_run_result.py +65 -0
  56. generic_ml_cache_core/application/domain/model/run/message.py +20 -0
  57. generic_ml_cache_core/application/domain/model/usage/__init__.py +1 -0
  58. generic_ml_cache_core/application/domain/model/usage/token_usage.py +53 -0
  59. generic_ml_cache_core/application/domain/model/usage/usage.py +108 -0
  60. generic_ml_cache_core/application/domain/service/__init__.py +1 -0
  61. generic_ml_cache_core/application/domain/service/cacheability.py +19 -0
  62. generic_ml_cache_core/application/domain/service/message_fingerprinting.py +25 -0
  63. generic_ml_cache_core/application/port/__init__.py +1 -0
  64. generic_ml_cache_core/application/port/inbound/__init__.py +1 -0
  65. generic_ml_cache_core/application/port/inbound/probe_command.py +35 -0
  66. generic_ml_cache_core/application/port/inbound/probe_use_case.py +19 -0
  67. generic_ml_cache_core/application/port/inbound/run_api_execution_command.py +40 -0
  68. generic_ml_cache_core/application/port/inbound/run_api_execution_use_case.py +20 -0
  69. generic_ml_cache_core/application/port/inbound/run_managed_local_execution_command.py +48 -0
  70. generic_ml_cache_core/application/port/inbound/run_managed_local_execution_use_case.py +25 -0
  71. generic_ml_cache_core/application/port/inbound/run_passthrough_execution_command.py +35 -0
  72. generic_ml_cache_core/application/port/inbound/run_passthrough_execution_use_case.py +20 -0
  73. generic_ml_cache_core/application/port/out/__init__.py +1 -0
  74. generic_ml_cache_core/application/port/out/api_client_port.py +26 -0
  75. generic_ml_cache_core/application/port/out/base.py +272 -0
  76. generic_ml_cache_core/application/port/out/blob_store_port.py +37 -0
  77. generic_ml_cache_core/application/port/out/client_runner_port.py +26 -0
  78. generic_ml_cache_core/application/port/out/clock_port.py +22 -0
  79. generic_ml_cache_core/application/port/out/execution_repository_port.py +40 -0
  80. generic_ml_cache_core/application/port/out/file_fingerprint_port.py +25 -0
  81. generic_ml_cache_core/application/port/out/metrics_port.py +54 -0
  82. generic_ml_cache_core/application/port/out/passthrough_runner_port.py +25 -0
  83. generic_ml_cache_core/application/usecase/__init__.py +1 -0
  84. generic_ml_cache_core/application/usecase/cached_ml_execution_service.py +198 -0
  85. generic_ml_cache_core/application/usecase/call_identity_building.py +60 -0
  86. generic_ml_cache_core/application/usecase/journal_events.py +19 -0
  87. generic_ml_cache_core/application/usecase/probe_service.py +44 -0
  88. generic_ml_cache_core/application/usecase/run_api_execution_service.py +69 -0
  89. generic_ml_cache_core/application/usecase/run_managed_local_execution_service.py +84 -0
  90. generic_ml_cache_core/application/usecase/run_passthrough_execution_service.py +67 -0
  91. generic_ml_cache_core/common/__init__.py +1 -0
  92. generic_ml_cache_core/common/checksum.py +82 -0
  93. generic_ml_cache_core/common/errors.py +76 -0
  94. generic_ml_cache_core/stream.py +65 -0
  95. generic_ml_cache_core-0.2.0.dist-info/METADATA +104 -0
  96. generic_ml_cache_core-0.2.0.dist-info/RECORD +99 -0
  97. generic_ml_cache_core-0.2.0.dist-info/WHEEL +4 -0
  98. generic_ml_cache_core-0.2.0.dist-info/licenses/LICENSE +201 -0
  99. generic_ml_cache_core-0.2.0.dist-info/licenses/NOTICE +8 -0
@@ -0,0 +1,67 @@
1
+ # SPDX-FileCopyrightText: 2026 Daniel Slobozian
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """RunPassthroughExecutionService."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Tuple
8
+
9
+ from generic_ml_cache_core.application.domain.model.identity.call_identity import CallIdentity
10
+ from generic_ml_cache_core.application.domain.model.run.client_run_result import ClientRunResult
11
+ from generic_ml_cache_core.application.domain.model.execution.execution_kind import ExecutionKind
12
+ from generic_ml_cache_core.application.domain.model.identity.passthrough_call_identity import (
13
+ PassthroughCallIdentity,
14
+ )
15
+ from generic_ml_cache_core.application.port.inbound.run_passthrough_execution_command import (
16
+ RunPassthroughExecutionCommand,
17
+ )
18
+ from generic_ml_cache_core.application.port.inbound.run_passthrough_execution_use_case import (
19
+ RunPassthroughExecutionUseCase,
20
+ )
21
+ from generic_ml_cache_core.application.port.out.blob_store_port import BlobStorePort
22
+ from generic_ml_cache_core.application.port.out.execution_repository_port import (
23
+ ExecutionRepositoryPort,
24
+ )
25
+ from generic_ml_cache_core.application.port.out.metrics_port import MetricsPort
26
+ from generic_ml_cache_core.application.port.out.passthrough_runner_port import PassthroughRunnerPort
27
+ from generic_ml_cache_core.application.usecase.cached_ml_execution_service import (
28
+ CachedMlExecutionService,
29
+ )
30
+ from generic_ml_cache_core.common.checksum import fingerprint_arguments
31
+
32
+
33
+ class RunPassthroughExecutionService(CachedMlExecutionService, RunPassthroughExecutionUseCase):
34
+ """Record-or-replay a passthrough (alias) client call.
35
+
36
+ Implements the inbound port over the shared cached-execution flow, supplying
37
+ the passthrough specifics: the identity is the client plus a fingerprint of
38
+ the opaque native args, the client runs via the passthrough runner (no
39
+ isolation, no file capture), and executions are tagged LOCAL_PASSTHROUGH. A
40
+ passthrough is always cacheable, so it keeps the base's default.
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ passthrough_runner: PassthroughRunnerPort,
46
+ blob_store: BlobStorePort,
47
+ repository: ExecutionRepositoryPort,
48
+ metrics: MetricsPort,
49
+ ) -> None:
50
+ super().__init__(blob_store, repository, metrics)
51
+ self._passthrough_runner = passthrough_runner
52
+
53
+ def _build_identity(self, command: RunPassthroughExecutionCommand) -> CallIdentity:
54
+ return PassthroughCallIdentity(
55
+ client=command.client,
56
+ native_args_fingerprint=fingerprint_arguments(command.native_args),
57
+ )
58
+
59
+ def _run_client(self, command: RunPassthroughExecutionCommand) -> ClientRunResult:
60
+ return self._passthrough_runner.run(command.client, command.native_args)
61
+
62
+ def _execution_kind(self) -> ExecutionKind:
63
+ return ExecutionKind.LOCAL_PASSTHROUGH
64
+
65
+ def _journal_fields(self, command: RunPassthroughExecutionCommand) -> Tuple[str, str, str]:
66
+ # A passthrough has no modelled model/effort — only the client is known.
67
+ return command.client, "", ""
@@ -0,0 +1 @@
1
+ """Cross-cutting leaves shared across the package (errors, checksums, constants)."""
@@ -0,0 +1,82 @@
1
+ # SPDX-FileCopyrightText: 2026 Daniel Slobozian
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """Container-independent checksumming of cache inputs.
4
+
5
+ The whole point of this module is one invariant:
6
+
7
+ The same *text* must yield the same checksum regardless of how that text
8
+ happened to be stored -- whether it lived in a standalone file on disk or
9
+ inside a JSON string field.
10
+
11
+ We achieve that by hashing the *decoded* UTF-8 text of each field, never the
12
+ bytes of whatever container (file, JSON document) carried it. Newlines, tabs
13
+ and other whitespace are meaningful and are never stripped.
14
+
15
+ Each field is length-prefixed before hashing so that, e.g., ``{"context": "ab",
16
+ "prompt": "c"}`` can never collide with ``{"context": "a", "prompt": "bc"}``.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import hashlib
22
+ from typing import Mapping, Sequence
23
+
24
+ # Control characters used purely as internal framing while hashing. They never
25
+ # touch user data and never appear in the stored record on disk.
26
+ _FIELD_SEP = b"\x1f" # unit separator
27
+ _RECORD_SEP = b"\x1e" # record separator
28
+ # Separates ordered arguments before hashing; order is significant (CLI flags
29
+ # are positional), so the join preserves it.
30
+ _ARGUMENT_SEP = "\x00"
31
+
32
+
33
+ def text_checksum(text: str) -> str:
34
+ """SHA-256 of a single decoded string's UTF-8 bytes."""
35
+ return hashlib.sha256(text.encode("utf-8")).hexdigest()
36
+
37
+
38
+ def file_content_fingerprint(data: bytes) -> str:
39
+ """The one shared rule for fingerprinting a declared input file's content.
40
+
41
+ SHA-256 of the raw bytes -- binary-safe, so any file type fingerprints the
42
+ same way regardless of encoding. This is the single function every front
43
+ door (CLI, daemon, library consumer) must call; it is imported directly,
44
+ never reimplemented, so two front doors can never derive different keys for
45
+ the same file and silently miss each other's cache.
46
+ """
47
+ return hashlib.sha256(data).hexdigest()
48
+
49
+
50
+ def fingerprint_arguments(arguments: Sequence[str]) -> str:
51
+ """Fingerprint an ordered argument list into the key.
52
+
53
+ The raw arguments may carry secrets, so only their digest is ever keyed or
54
+ stored. Order is significant; the join with a control separator preserves it.
55
+ """
56
+ return text_checksum(_ARGUMENT_SEP.join(arguments))
57
+
58
+
59
+ def checksum_input_data(input_data: Mapping[str, str]) -> str:
60
+ """Return the container-independent SHA-256 checksum of ``input_data``.
61
+
62
+ ``input_data`` is the cache input mapping, e.g. ``{"context": ..., "prompt":
63
+ ...}``. Keys are hashed in sorted order so the result does not depend on dict
64
+ ordering. Values must be ``str`` -- the cache is deliberately *dumb*, so it is
65
+ the caller's job to make the text deterministic.
66
+ """
67
+ digest = hashlib.sha256()
68
+ for key in sorted(input_data):
69
+ value = input_data[key]
70
+ if not isinstance(value, str):
71
+ raise TypeError(
72
+ f"input_data[{key!r}] must be str (the cache hashes decoded text, "
73
+ f"not bytes or objects); got {type(value).__name__}"
74
+ )
75
+ encoded = value.encode("utf-8")
76
+ digest.update(key.encode("utf-8"))
77
+ digest.update(_FIELD_SEP)
78
+ digest.update(str(len(encoded)).encode("ascii"))
79
+ digest.update(_FIELD_SEP)
80
+ digest.update(encoded)
81
+ digest.update(_RECORD_SEP)
82
+ return digest.hexdigest()
@@ -0,0 +1,76 @@
1
+ # SPDX-FileCopyrightText: 2026 Daniel Slobozian
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """Exception types raised by generic-ml-cache."""
4
+
5
+ from __future__ import annotations
6
+
7
+
8
+ class CacheError(Exception):
9
+ """Base class for all generic-ml-cache errors."""
10
+
11
+
12
+ class CacheMiss(CacheError):
13
+ """Raised in offline mode when no stored execution matches the request.
14
+
15
+ Offline mode is a *knowing* switch to replay-only: a miss is an error, never
16
+ a silent fall-through to a real call.
17
+ """
18
+
19
+
20
+ class UnknownClient(CacheError):
21
+ """Raised when no adapter is registered for the requested client name."""
22
+
23
+
24
+ class ConfigError(CacheError):
25
+ """Raised when the optional config file or a config env var is invalid.
26
+
27
+ A missing config file is never an error -- it just means built-in defaults
28
+ apply. This is only for a file that exists but cannot be parsed, or a value
29
+ (mode, timeout) that is not understood.
30
+ """
31
+
32
+
33
+ class ClientNotFound(CacheError):
34
+ """Raised when the client executable cannot be located on the system."""
35
+
36
+
37
+ class CommandLineTooLong(CacheError):
38
+ """Raised before launch when the assembled command line would exceed the
39
+ operating system's argument-size limit.
40
+
41
+ Only a client that receives the prompt as a command-line argument
42
+ (cursor-agent, which has no stdin path) can hit this; claude and codex take the
43
+ prompt on stdin and are unaffected. Raising it up front turns an opaque OS
44
+ "argument list too long" error (or a silent Windows failure) into a clear
45
+ message that names the size, the limit, and the remedy.
46
+ """
47
+
48
+
49
+ class InputFileError(CacheError):
50
+ """Raised when a declared input file cannot be read for fingerprinting.
51
+
52
+ The path does not point to a regular file, or the bytes could not be read.
53
+ The filesystem fingerprint adapter translates the foreign ``OSError`` into
54
+ this cause-named exception so the core never sees a library error type.
55
+ """
56
+
57
+
58
+ class ArtifactBlobMissing(CacheError):
59
+ """Raised when hydrating an execution whose artifact references a blob that
60
+ the blob store no longer holds.
61
+
62
+ The structured record says the output was persisted, but the bytes are gone
63
+ (an out-of-band deletion, a half-completed prune). The engine fails loud
64
+ rather than returning a silently empty result.
65
+ """
66
+
67
+
68
+ class RunInterrupted(Exception):
69
+ """Raised when a real client run is stopped by a signal from the caller (the
70
+ workflow engine) before it finished.
71
+
72
+ Deliberately **not** a ``CacheError``: it is not a fault but a requested stop,
73
+ and it must never be recorded as an execution -- an interrupted call is not a
74
+ result. The CLI maps it to a distinct exit code so a stop is distinguishable
75
+ from a failure.
76
+ """
@@ -0,0 +1,65 @@
1
+ # SPDX-FileCopyrightText: 2026 Daniel Slobozian
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """Live progress stream: an opt-in NDJSON event file written as a call runs.
4
+
5
+ The cache is the one running the client, so it is the right place to surface what
6
+ the client is doing *right now* -- for a human watching a long call, and (later)
7
+ for the workflow engine relaying progress to its own user.
8
+
9
+ Design:
10
+
11
+ * **Display-only.** The stream never changes what the cache records or the cache
12
+ key. It is a *view* of the run, not a second source of truth.
13
+ * **A regular file** is the transport. It is the one streaming channel that
14
+ behaves identically on Linux, macOS and Windows -- no sockets, no named pipes
15
+ (POSIX-only), no ``select``. A consumer (a human ``tail -f``, or a parent
16
+ process reading line by line) just opens it read-only and reads new lines.
17
+ * **NDJSON / JSON Lines.** One self-contained JSON object per line, ``\\n``
18
+ separated, flushed as written, so each event is readable the moment it lands
19
+ and the file is appendable without parsing what came before.
20
+ * **Best-effort.** A write that fails (full disk, bad path) is dropped, never
21
+ raised -- a progress view must not be able to break the real call.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import json
27
+ import time
28
+ from pathlib import Path
29
+ from typing import Any, Optional
30
+
31
+
32
+ class StreamWriter:
33
+ """Append NDJSON progress events to ``path``. Safe to construct even if the
34
+ path is unwritable: streaming is then silently disabled and the call runs
35
+ unaffected."""
36
+
37
+ def __init__(self, path: Path) -> None:
38
+ self._fh: Optional[Any] = None
39
+ try:
40
+ path.parent.mkdir(parents=True, exist_ok=True)
41
+ # Text append; newline="\n" writes bare line feeds on every OS (no
42
+ # CRLF translation), which is what NDJSON consumers expect.
43
+ self._fh = open(path, "a", encoding="utf-8", newline="\n")
44
+ except OSError:
45
+ self._fh = None # streaming disabled; the run still proceeds
46
+
47
+ def event(self, kind: str, **fields: Any) -> None:
48
+ """Write one event line: ``{"ts": <epoch>, "kind": <kind>, ...}``.
49
+ Fields that are ``None`` are omitted so the line stays compact."""
50
+ if self._fh is None:
51
+ return
52
+ record = {"ts": round(time.time(), 3), "kind": kind}
53
+ record.update({k: v for k, v in fields.items() if v is not None})
54
+ try:
55
+ self._fh.write(json.dumps(record, ensure_ascii=False) + "\n")
56
+ self._fh.flush()
57
+ except (OSError, ValueError, TypeError):
58
+ pass # drop the event rather than fail the run
59
+
60
+ def close(self) -> None:
61
+ if self._fh is not None:
62
+ try:
63
+ self._fh.close()
64
+ finally:
65
+ self._fh = None
@@ -0,0 +1,104 @@
1
+ Metadata-Version: 2.4
2
+ Name: generic-ml-cache-core
3
+ Version: 0.2.0
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
+ Project-URL: Homepage, https://github.com/danielslobozian/generic-ml-cache
6
+ Project-URL: Repository, https://github.com/danielslobozian/generic-ml-cache
7
+ Project-URL: Issues, https://github.com/danielslobozian/generic-ml-cache/issues
8
+ Project-URL: Changelog, https://github.com/danielslobozian/generic-ml-cache/blob/main/CHANGELOG.md
9
+ Author: Daniel Slobozian
10
+ License-Expression: Apache-2.0
11
+ License-File: LICENSE
12
+ License-File: NOTICE
13
+ Keywords: ai,cache,hexagonal,library,llm,ports-and-adapters,replay
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: Apache Software License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: Software Development :: Libraries
25
+ Requires-Python: >=3.9
26
+ Provides-Extra: dev
27
+ Requires-Dist: coverage>=7; extra == 'dev'
28
+ Requires-Dist: pytest-cov; extra == 'dev'
29
+ Requires-Dist: pytest>=7; extra == 'dev'
30
+ Requires-Dist: ruff>=0.15; extra == 'dev'
31
+ Requires-Dist: vulture>=2; extra == 'dev'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # generic-ml-cache-core
35
+
36
+ #### The hexagonal engine behind gmlcache — embeddable, stateless, dependency-free
37
+
38
+ [![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-2563eb?style=flat-square)](https://github.com/danielslobozian/generic-ml-cache/blob/main/LICENSE)
39
+ [![Status: Alpha](https://img.shields.io/badge/Status-Alpha-d97706?style=flat-square)](https://github.com/danielslobozian/generic-ml-cache/blob/main/docs/ROADMAP.md)
40
+
41
+ The reusable **engine** behind
42
+ [`gmlcache`](https://github.com/danielslobozian/generic-ml-cache/tree/main/packages/cli):
43
+ record a real ML client (or API) call once, replay it by its content key. It contains
44
+ the domain model, the use cases, the port contracts, **and the default outbound
45
+ adapters** (SQLite execution repository, filesystem blob store, the
46
+ claude/codex/cursor client runner, the API client, metrics, clock, fingerprinting) —
47
+ plus the `build_use_cases` composition factory.
48
+
49
+ Pure Python, **zero runtime dependencies**, and **stateless**: it bakes in *structure*
50
+ (table names, blob naming, schema) but no *location* — you inject the data source.
51
+
52
+ ## Install
53
+
54
+ ```bash
55
+ pip install generic-ml-cache-core
56
+ ```
57
+
58
+ ## Embed it
59
+
60
+ Hand the library a data source and it wires the engine for you:
61
+
62
+ ```python
63
+ from generic_ml_cache_core import build_use_cases
64
+ from generic_ml_cache_core.application.port.inbound.run_managed_local_execution_command import (
65
+ RunManagedLocalExecutionCommand,
66
+ )
67
+
68
+ wired = build_use_cases(store_root="/path/you/choose") # you provide the data source
69
+ command = RunManagedLocalExecutionCommand(
70
+ client="claude", model="sonnet", effort="", context="", prompt="…",
71
+ )
72
+ execution = wired.run_managed.execute(command) # records on a miss, replays on a hit
73
+ ```
74
+
75
+ You reuse the shipped adapters by injecting a data source — you never reimplement them
76
+ (the **Spring Batch** model: the framework ships the writers, you provide the
77
+ connection). Need a different store? Construct the use cases yourself against the
78
+ ports and pass your own adapter.
79
+
80
+ ## What's inside
81
+
82
+ - **Domain model** — executions, polymorphic call identities, artifacts, usage.
83
+ - **Use cases** — managed-local / passthrough / API runs, and probe (check).
84
+ - **Ports** (`application/port/...`) — client runner, blob store, execution repository,
85
+ metrics, clock, fingerprint, API client.
86
+ - **Default adapters** (`adapter/out/...`) + the `build_use_cases` composition factory.
87
+ - **`generic_ml_cache_core.testing.InMemoryExecutionRepository`** — a dependency-free
88
+ reference adapter to test your code against the ports.
89
+
90
+ Inbound drivers —
91
+ [`gmlcache`](https://github.com/danielslobozian/generic-ml-cache/tree/main/packages/cli)
92
+ today, a daemon later — map their surface (a terminal, a REST API) onto these public
93
+ APIs; the core itself has no UI and reads no config file.
94
+
95
+ ## Links
96
+
97
+ - **Repository & docs:** <https://github.com/danielslobozian/generic-ml-cache>
98
+ - **Changelog** (both packages, versioned in lockstep): [`CHANGELOG.md`](https://github.com/danielslobozian/generic-ml-cache/blob/main/CHANGELOG.md)
99
+ - **Security policy:** [`SECURITY.md`](https://github.com/danielslobozian/generic-ml-cache/blob/main/SECURITY.md)
100
+
101
+ ## License
102
+
103
+ Apache-2.0 — see [`LICENSE`](https://github.com/danielslobozian/generic-ml-cache/blob/main/LICENSE)
104
+ and [`NOTICE`](https://github.com/danielslobozian/generic-ml-cache/blob/main/NOTICE).
@@ -0,0 +1,99 @@
1
+ generic_ml_cache_core/__init__.py,sha256=YFP5MttqlFpwig_h08W_a3QqbwyCD3Jhac60HbFeSbo,2053
2
+ generic_ml_cache_core/stream.py,sha256=a2hIsedn7zqywkBe5cMw-nV5rU02CkTfyEYOJ_ypWIc,2793
3
+ generic_ml_cache_core/adapter/__init__.py,sha256=nuehNF41p14kRWK8rgjE0dNwDo6-Jqqa4M2wTMTx8Z4,31
4
+ generic_ml_cache_core/adapter/inbound/__init__.py,sha256=nuehNF41p14kRWK8rgjE0dNwDo6-Jqqa4M2wTMTx8Z4,31
5
+ generic_ml_cache_core/adapter/inbound/composition.py,sha256=gVmHhSsMtf2D_PHpm5pxJZudnVsXLVexTvfTRQbgm3g,4017
6
+ generic_ml_cache_core/adapter/out/__init__.py,sha256=nuehNF41p14kRWK8rgjE0dNwDo6-Jqqa4M2wTMTx8Z4,31
7
+ generic_ml_cache_core/adapter/out/api/__init__.py,sha256=nuehNF41p14kRWK8rgjE0dNwDo6-Jqqa4M2wTMTx8Z4,31
8
+ generic_ml_cache_core/adapter/out/api/stub_api_client_adapter.py,sha256=KPlpGue5V64XBcZCmZy_NQjowdCajkcTA2rz6VFpugU,1521
9
+ generic_ml_cache_core/adapter/out/client/__init__.py,sha256=54Rt2Fjo_46yhXAM-Xu_TeszJVXvvMRwXJtQq8bbVRQ,978
10
+ generic_ml_cache_core/adapter/out/client/claude.py,sha256=upoXwuzopFIfLimLQCt1CrFhpe6a08-AWdfLgUzZYrY,9310
11
+ generic_ml_cache_core/adapter/out/client/codex.py,sha256=T4uey-iC4AIbKGJMvEfl6mOPgQkR9osBxh9M-m4tgCU,7400
12
+ generic_ml_cache_core/adapter/out/client/cursor.py,sha256=9cstycJXAGm0DfmrmaKCyTPs0aExq1NkBtO1AAIGiZM,9590
13
+ generic_ml_cache_core/adapter/out/client/discover.py,sha256=TUeQQP5M8y8VKUJqCI6Cshvrh1pmTVUwIQrshZpjzFU,4882
14
+ generic_ml_cache_core/adapter/out/client/isolation.py,sha256=q2ON_VOTBPZKnK-CFRuPyr4uSYQYpMoypm1R68-N0Cg,15851
15
+ generic_ml_cache_core/adapter/out/client/local_client_runner.py,sha256=JmNlDFHeFowEfSWFeAyPM6f5X40fzo4H7eXZN222chc,2435
16
+ generic_ml_cache_core/adapter/out/client/passthrough_client_runner.py,sha256=ae7v0AJc2PXTY0vOMPd2gXDU73qcTxVEbAAkgg8APlw,1919
17
+ generic_ml_cache_core/adapter/out/client/prime_directive.py,sha256=S5O8ggEQJWe61QI1IQhFot4qlTsQbuyF7nOgWWVBGms,2525
18
+ generic_ml_cache_core/adapter/out/client/registry.py,sha256=kqKoTxaUbPKcbn3izXdbqSgCbni9ElI0x43ZUhY0KkU,1011
19
+ generic_ml_cache_core/adapter/out/clock/__init__.py,sha256=nuehNF41p14kRWK8rgjE0dNwDo6-Jqqa4M2wTMTx8Z4,31
20
+ generic_ml_cache_core/adapter/out/clock/system_clock.py,sha256=wOhuOmWKF4VAo-7X2ZKOOebhNo2QZJATatmWuwt15MA,466
21
+ generic_ml_cache_core/adapter/out/fingerprint/__init__.py,sha256=nuehNF41p14kRWK8rgjE0dNwDo6-Jqqa4M2wTMTx8Z4,31
22
+ generic_ml_cache_core/adapter/out/fingerprint/filesystem_file_fingerprint.py,sha256=_TErnfPykePUULxXm-uF6AIKJtfVt-pD1d-8BtSCltU,1235
23
+ generic_ml_cache_core/adapter/out/metrics/__init__.py,sha256=nuehNF41p14kRWK8rgjE0dNwDo6-Jqqa4M2wTMTx8Z4,31
24
+ generic_ml_cache_core/adapter/out/metrics/access_registry.py,sha256=oUfI3tzokgohq-irsGhpLdc8wXBZkjQmzkz_qyWz_BU,5286
25
+ generic_ml_cache_core/adapter/out/metrics/journal_metrics.py,sha256=ypP0JAo42sbvtlv2nXGSjE7lAygmONt6tiNuEanudJE,1503
26
+ generic_ml_cache_core/adapter/out/persistence/__init__.py,sha256=nuehNF41p14kRWK8rgjE0dNwDo6-Jqqa4M2wTMTx8Z4,31
27
+ generic_ml_cache_core/adapter/out/persistence/call_identity_serialization.py,sha256=tto1R0n4hy2Tgbvhrs4ugZao44Gvvj47a0FHxDEMJN8,4069
28
+ generic_ml_cache_core/adapter/out/persistence/in_memory_execution_repository.py,sha256=yJ_1CoNU5iLq42kvYTxbEeFMIBIJf9AiFSOlAvH7JsY,3033
29
+ generic_ml_cache_core/adapter/out/persistence/sqlite_execution_repository.py,sha256=sdInMD93fXvdcYSM6NtVHCRk0hS6TtGaS6fz9p_7qt4,15356
30
+ generic_ml_cache_core/adapter/out/storage/__init__.py,sha256=nuehNF41p14kRWK8rgjE0dNwDo6-Jqqa4M2wTMTx8Z4,31
31
+ generic_ml_cache_core/adapter/out/storage/filesystem_blob_store.py,sha256=KcvDtLkRc8kv04nH_HnmBqjx5hVchW-J3XqCM0Oarcc,1565
32
+ generic_ml_cache_core/application/__init__.py,sha256=nuehNF41p14kRWK8rgjE0dNwDo6-Jqqa4M2wTMTx8Z4,31
33
+ generic_ml_cache_core/application/domain/__init__.py,sha256=nuehNF41p14kRWK8rgjE0dNwDo6-Jqqa4M2wTMTx8Z4,31
34
+ generic_ml_cache_core/application/domain/model/__init__.py,sha256=nuehNF41p14kRWK8rgjE0dNwDo6-Jqqa4M2wTMTx8Z4,31
35
+ generic_ml_cache_core/application/domain/model/client_status.py,sha256=uSNwPAqvCyit7jknjts_hf6Oq5N5EbbpvanqRQCjFkQ,477
36
+ generic_ml_cache_core/application/domain/model/model_info.py,sha256=Rzyl0WB7Us1ZrEbHOV6tTksQsAu64J2rIf58aiOtMgI,511
37
+ generic_ml_cache_core/application/domain/model/model_listing.py,sha256=fz54OyNNwQZ1yXOsO-U5Z_Sm7fED5tZDeaSNSaPKXgo,860
38
+ generic_ml_cache_core/application/domain/model/parsed_output.py,sha256=kfxYCLtkdMLr4mAv0YSe8rDOOqeIH-mFmHOdS8tm1Bk,738
39
+ generic_ml_cache_core/application/domain/model/execution/__init__.py,sha256=nuehNF41p14kRWK8rgjE0dNwDo6-Jqqa4M2wTMTx8Z4,31
40
+ generic_ml_cache_core/application/domain/model/execution/artifact.py,sha256=b1lXMoDxhJIzOiex2u1F5Q9jHGpf9hDMUpF4bugCkBM,2262
41
+ generic_ml_cache_core/application/domain/model/execution/execution_failure.py,sha256=KvUZyLivH6YsjJd5eaZitE21sRW0UJhrFuq_-ulU4-E,931
42
+ generic_ml_cache_core/application/domain/model/execution/execution_kind.py,sha256=_fBpNKWQS8AmcgcTglFzDCIyma9bmalJJteAw__QjbI,1033
43
+ generic_ml_cache_core/application/domain/model/execution/execution_state.py,sha256=N4TNnuRuusr0ROMiHdiOAn-r1zibhznGv2fJOnxGNkg,516
44
+ generic_ml_cache_core/application/domain/model/execution/ml_execution.py,sha256=3GYdv2_mZ36ZYdKMCZK-KhJKHguKDt5heWB3ajSd8Bc,1775
45
+ generic_ml_cache_core/application/domain/model/identity/__init__.py,sha256=nuehNF41p14kRWK8rgjE0dNwDo6-Jqqa4M2wTMTx8Z4,31
46
+ generic_ml_cache_core/application/domain/model/identity/api_call_identity.py,sha256=A-RRECEr0B2KxokgOi-iwKCJU3bIQkVggAcIjhX9KAk,1212
47
+ generic_ml_cache_core/application/domain/model/identity/call_identity.py,sha256=Y_XI0mEvhmVaQD51jClgS1nZI8RV4PDoiDK8zEcHcjw,887
48
+ generic_ml_cache_core/application/domain/model/identity/managed_call_identity.py,sha256=3nIqidUEwPxI5WzgFD0x-ZDIzo1wC9yLboxhz6v-UkI,2300
49
+ generic_ml_cache_core/application/domain/model/identity/passthrough_call_identity.py,sha256=oSyRvuukrQRqdG2wf1HTB_TM0f9a8Kcuq8wcO0HxOXo,1285
50
+ generic_ml_cache_core/application/domain/model/probe/__init__.py,sha256=nuehNF41p14kRWK8rgjE0dNwDo6-Jqqa4M2wTMTx8Z4,31
51
+ generic_ml_cache_core/application/domain/model/probe/probe_report.py,sha256=JkK1dcY_D2Q7to087Sk5I1Aa1nOf29aFizac5TeSXI8,868
52
+ generic_ml_cache_core/application/domain/model/probe/probe_status.py,sha256=CEDX4hGAEZL36HlKxzOYb08YygFCLwhdZqmSfUwyx2k,381
53
+ generic_ml_cache_core/application/domain/model/run/__init__.py,sha256=nuehNF41p14kRWK8rgjE0dNwDo6-Jqqa4M2wTMTx8Z4,31
54
+ generic_ml_cache_core/application/domain/model/run/cache_mode.py,sha256=_DnxIzowMTiZl3gWiuFXWtNI02vayXlOYdfm9iqela4,557
55
+ generic_ml_cache_core/application/domain/model/run/client_run_request.py,sha256=ymp_azgY9D3eU8W-VjP5FiQKHhRtPg980Fi7s-I2AmE,1285
56
+ generic_ml_cache_core/application/domain/model/run/client_run_result.py,sha256=3PwE5rzftECOOhiyq6TdyoRpKIdjY98WKsqWnTNXH_c,2311
57
+ generic_ml_cache_core/application/domain/model/run/message.py,sha256=wqCwlvJAlzE-1R7XXMmIyshj8aJLB21f6x864Td_cLE,562
58
+ generic_ml_cache_core/application/domain/model/usage/__init__.py,sha256=nuehNF41p14kRWK8rgjE0dNwDo6-Jqqa4M2wTMTx8Z4,31
59
+ generic_ml_cache_core/application/domain/model/usage/token_usage.py,sha256=r1XTjyBzfoWtg_ZnoNza23YQQm4BBPbgj93L0X6An2E,2186
60
+ generic_ml_cache_core/application/domain/model/usage/usage.py,sha256=A-KZRE0A7rIj0o5VjOVqDFfaJeUpgWUl4EMJ4MBkWao,5037
61
+ generic_ml_cache_core/application/domain/service/__init__.py,sha256=nuehNF41p14kRWK8rgjE0dNwDo6-Jqqa4M2wTMTx8Z4,31
62
+ generic_ml_cache_core/application/domain/service/cacheability.py,sha256=59YHGtzle-VEUyXsv79pHwp5qQ3dFoxr2HYM9lXTG8Q,806
63
+ generic_ml_cache_core/application/domain/service/message_fingerprinting.py,sha256=XhnFmvXky2F7eqfYVsGyQo2uGskIv7Jr3_FRjUyBp88,971
64
+ generic_ml_cache_core/application/port/__init__.py,sha256=nuehNF41p14kRWK8rgjE0dNwDo6-Jqqa4M2wTMTx8Z4,31
65
+ generic_ml_cache_core/application/port/inbound/__init__.py,sha256=nuehNF41p14kRWK8rgjE0dNwDo6-Jqqa4M2wTMTx8Z4,31
66
+ generic_ml_cache_core/application/port/inbound/probe_command.py,sha256=YTz0CFqY-tDIrJFLdItGGdGMNpyGViXSpAzpbpMpqBk,1123
67
+ generic_ml_cache_core/application/port/inbound/probe_use_case.py,sha256=iA1ljXweAz-TArbsmGA6TXmszc-sC1Brfeg59rey9j8,726
68
+ generic_ml_cache_core/application/port/inbound/run_api_execution_command.py,sha256=Le1eIyrSiKQIpV0rHtjoI_9eh5M_pZMPuS5cvAQqzlA,1491
69
+ generic_ml_cache_core/application/port/inbound/run_api_execution_use_case.py,sha256=IipBD4plrpHZTafWaHkSOATxvOJgQ4gDGDSAjZogffc,697
70
+ generic_ml_cache_core/application/port/inbound/run_managed_local_execution_command.py,sha256=ILuvQ5R-HMmL0RMByPkj0GRrmPOQjpFMBmNq5GNAjGM,1760
71
+ generic_ml_cache_core/application/port/inbound/run_managed_local_execution_use_case.py,sha256=xDLfF-02weweXlQRXimZPJUplF5tNtAhabQuN2hW39c,923
72
+ generic_ml_cache_core/application/port/inbound/run_passthrough_execution_command.py,sha256=GtAoe5UXCGPS3_PcJ-PPNQ-upFMMSb-AdrkB7kBdDM0,1239
73
+ generic_ml_cache_core/application/port/inbound/run_passthrough_execution_use_case.py,sha256=T_I7IpB-Qwia6MGg17IJ7kQ-m1bjmhrQjODW_cyiJek,749
74
+ generic_ml_cache_core/application/port/out/__init__.py,sha256=nuehNF41p14kRWK8rgjE0dNwDo6-Jqqa4M2wTMTx8Z4,31
75
+ generic_ml_cache_core/application/port/out/api_client_port.py,sha256=16CjfOFjEXFabsVpQq_Gk7X7-MF2qMz2DuKVCsgTNWc,1047
76
+ generic_ml_cache_core/application/port/out/base.py,sha256=_YXmS2xcIJL8U3lzJswAKOM8QrQkY34GP8OnUhGy_Zc,12958
77
+ generic_ml_cache_core/application/port/out/blob_store_port.py,sha256=llWgt3Fsl55MlJGDGoOxHDijDfPHivVqzPqGBbgxA9g,1336
78
+ generic_ml_cache_core/application/port/out/client_runner_port.py,sha256=ZJxMrAcrEtLgdK3yTJe3Cv6bqjI2OjcfIcOMxfQZKKM,1081
79
+ generic_ml_cache_core/application/port/out/clock_port.py,sha256=Ai5JxLlvIaQ8JXKbCWJ_QtZZjkucy6sK25nSddxk7Zw,699
80
+ generic_ml_cache_core/application/port/out/execution_repository_port.py,sha256=j86EyxbzHSaMubFJ3cg1xdx4Vpj3c5blq7oRhhUFYoo,1708
81
+ generic_ml_cache_core/application/port/out/file_fingerprint_port.py,sha256=ZXyJq7LFRVWfgpvYFTraAl5k5iU9vozckEciQ-hDs2U,907
82
+ generic_ml_cache_core/application/port/out/metrics_port.py,sha256=DxHL2eTKa7jd4UV846qcXP9fV8MZjEzo01j5Ja_-QbA,1634
83
+ generic_ml_cache_core/application/port/out/passthrough_runner_port.py,sha256=8wxsdJ3Lj65m0QPjOhm4inf8QqeEFhIZus8Do7cQTMI,968
84
+ generic_ml_cache_core/application/usecase/__init__.py,sha256=nuehNF41p14kRWK8rgjE0dNwDo6-Jqqa4M2wTMTx8Z4,31
85
+ generic_ml_cache_core/application/usecase/cached_ml_execution_service.py,sha256=ZEdtMoYAzOfVFqmNFfZrjIkzHnJtlCNxEtvDQ0aPGCI,8880
86
+ generic_ml_cache_core/application/usecase/call_identity_building.py,sha256=1giUpsOEp_zRuJbhMvvn11f6xvkK_-RJNNif1rNzZZY,2217
87
+ generic_ml_cache_core/application/usecase/journal_events.py,sha256=UrEVD-4hGQxTXQ9btjaazh_zuEqW0wJMjksA9civaRs,710
88
+ generic_ml_cache_core/application/usecase/probe_service.py,sha256=HgiXc6_PFnJF-mXN126h0VnJZleQUMcQGq1j1UaTw80,2040
89
+ generic_ml_cache_core/application/usecase/run_api_execution_service.py,sha256=XoLLpgKR0Sf9-FXKNrUF78__2VkGXMoLA22zLkpLoNM,2907
90
+ generic_ml_cache_core/application/usecase/run_managed_local_execution_service.py,sha256=QCCS3EWfye2pns0YbSfwpwRDSjX49CsScOWIldyoGHQ,3862
91
+ generic_ml_cache_core/application/usecase/run_passthrough_execution_service.py,sha256=x0hI_VE8Cp3cZBWiU7AVZeSJAD4AZ9y6rCZlR7epJEA,3048
92
+ generic_ml_cache_core/common/__init__.py,sha256=joTT8UX3dPEgh1e2GC8gq3crhw0i7C6J-HfS2vnP88Y,85
93
+ generic_ml_cache_core/common/checksum.py,sha256=1p9Yhsh3WmB7r0H5x5zUABMyg7_0D_TwgvxzUtZU-HM,3361
94
+ generic_ml_cache_core/common/errors.py,sha256=aP3Udm7ftusGdQQsrzgXFM9_GHc37Dd60dZy0vMz-XM,2745
95
+ generic_ml_cache_core-0.2.0.dist-info/METADATA,sha256=fGNRAlf4jHyImexYTqQtw99pq3WByCr7PaMn1DzA2Zg,5015
96
+ generic_ml_cache_core-0.2.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
97
+ generic_ml_cache_core-0.2.0.dist-info/licenses/LICENSE,sha256=de-gfE0q-xTYImzwC3dj3S7BxVhanf6RmIGjo_7y3aw,11357
98
+ generic_ml_cache_core-0.2.0.dist-info/licenses/NOTICE,sha256=hQoAdw5YSwg3GmeY8hmy9nyOh20La-Qc6mre-TKpn5M,329
99
+ generic_ml_cache_core-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any