matrx-assignment 0.1.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.
@@ -0,0 +1,271 @@
1
+ *.pyc
2
+ secrets/
3
+ ignore/
4
+ temp/
5
+ logs/
6
+ # The broad `logs/` rule above is for RUNTIME log output, but it also matched
7
+ # the dashboard's SOURCE directory and silently swallowed an entire feature's
8
+ # files (only the pre-existing index.tsx stayed tracked), breaking the prod
9
+ # Docker build with "Could not resolve ./structured-tab". Re-include the source.
10
+ !apps/dashboard/src/features/logs/
11
+ !apps/dashboard/src/features/logs/**
12
+ todo
13
+ text_notes/
14
+ aidream/secrets/2.env
15
+ automation_matrix/matrix_processing/temp/*
16
+ cd
17
+ # Byte-compiled / optimized / DLL files
18
+ __pycache__/
19
+ *.py[cod]
20
+ *$py.class
21
+
22
+ # C extensions
23
+ *.so
24
+ .venv/
25
+
26
+ # Distribution / packaging
27
+ .Python
28
+ build/
29
+ develop-eggs/
30
+ dist/
31
+ downloads/
32
+ eggs/
33
+ .eggs/
34
+ lib/
35
+ lib64/
36
+ # The blanket lib/ rule above is from the standard Python .gitignore template
37
+ # and was silently swallowing TS source under the SPA `src/lib/` folders.
38
+ # Re-allow them explicitly so frontend builds don't ship without their lib layer.
39
+ !apps/dashboard/src/lib/
40
+ !apps/dashboard/src/lib/**
41
+ !apps/workflow-studio/src/lib/
42
+ !apps/workflow-studio/src/lib/**
43
+ parts/
44
+ sdist/
45
+ var/
46
+ wheels/
47
+ share/python-wheels/
48
+ *.egg-info/
49
+ .installed.cfg
50
+ *.egg
51
+ MANIFEST
52
+
53
+ # PyInstaller
54
+ # Usually these files are written by a python script from a template
55
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
56
+ *.manifest
57
+ *.spec
58
+
59
+ # Installer logs
60
+ pip-log.txt
61
+ pip-delete-this-directory.txt
62
+
63
+ # Unit test / coverage reports
64
+ ai/tests/clean_response.json
65
+ ai/tests/cx_storage_response.json
66
+ ai/tests/execution_test.py
67
+ ai/tests/final_response.json
68
+ htmlcov/
69
+ .tox/
70
+ .nox/
71
+ .coverage
72
+ .coverage.*
73
+ .cache
74
+ nosetests.xml
75
+ coverage.xml
76
+ *.cover
77
+ *.py,cover
78
+ .hypothesis/
79
+ .pytest_cache/
80
+ cover/
81
+
82
+ # Translations
83
+ *.mo
84
+ *.pot
85
+
86
+ # Django stuff:
87
+ *.log
88
+ local_settings.py
89
+ db.sqlite3
90
+ db.sqlite3-journal
91
+
92
+ # Flask stuff:
93
+ instance/
94
+ .webassets-cache
95
+
96
+ # Scrapy stuff:
97
+ .scrapy
98
+
99
+ # Sphinx documentation
100
+ docs/_build/
101
+
102
+ # PyBuilder
103
+ .pybuilder/
104
+ target/
105
+
106
+ # Jupyter Notebook
107
+ .ipynb_checkpoints
108
+
109
+ # IPython
110
+ profile_default/
111
+ ipython_config.py
112
+
113
+ # pyenv
114
+ # For a library or package, you might want to ignore these files since the code is
115
+ # intended to run in multiple environments; otherwise, check them in:
116
+ # .python-version
117
+
118
+ # pipenv
119
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
120
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
121
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
122
+ # install all needed dependencies.
123
+ #Pipfile.lock
124
+
125
+ # poetry
126
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
127
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
128
+ # commonly ignored for libraries.
129
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
130
+
131
+ # pdm
132
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
133
+ #pdm.lock
134
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
135
+ # in version control.
136
+ # https://pdm.fming.dev/#use-with-ide
137
+ .pdm.toml
138
+
139
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
140
+ __pypackages__/
141
+
142
+ # Celery stuff
143
+ celerybeat-schedule
144
+ celerybeat.pid
145
+
146
+ # SageMath parsed files
147
+ *.sage.py
148
+
149
+ # Environments
150
+ .env
151
+ .env_remote
152
+ .venv
153
+ env/
154
+ venv/
155
+ ENV/
156
+ env.bak/
157
+ venv.bak/
158
+ .env.armanonly
159
+
160
+ # Spyder project settings
161
+ .spyderproject
162
+ .spyproject
163
+
164
+ # Rope project settings
165
+ .ropeproject
166
+
167
+ # mkdocs documentation
168
+ /site
169
+
170
+ # mypy
171
+ .mypy_cache/
172
+ .dmypy.json
173
+ dmypy.json
174
+
175
+ # Pyre type checker
176
+ .pyre/
177
+
178
+ # random armani files
179
+ /armani_dev/secrets/
180
+ /armani/
181
+ /_armani/
182
+
183
+
184
+
185
+ # pytype static type analyzer
186
+ .pytype/
187
+
188
+ # Cython debug symbols
189
+ cython_debug/
190
+
191
+ .idea/
192
+ .vscode/
193
+ /node_modules/
194
+
195
+ # Frontend pnpm workspace (apps/) — node_modules at the workspace root and any
196
+ # member, plus Vite caches and build output. The unified lockfile (apps/pnpm-lock.yaml)
197
+ # IS committed; everything below is regenerated.
198
+ node_modules/
199
+ apps/**/.vite/
200
+ apps/**/dist/
201
+ .vite/
202
+
203
+ dump.rdb
204
+
205
+ frontend/
206
+
207
+ # AME Temp Files and directory structure
208
+ # Ignore all files in the temp directory and its subdirectories
209
+ /temp/**/*
210
+ /tmp/**/*
211
+
212
+ # Allow .gitkeep files to retain directory structure
213
+ !/temp/**/.gitkeep
214
+ !/tmp/**/.gitkeep
215
+
216
+ # Armani
217
+ .history*
218
+ .history/
219
+ local_data/
220
+ local_reports_data/
221
+ webscraper/quick_scrapes/temp/
222
+ automation_matrix/ai_apis/fireworks/_dev/*
223
+ automation_matrix/ai_apis/fireworks/_dev/fireworks_sample.py
224
+ *.pdf
225
+ *.flac
226
+ *.mp3
227
+ *.wav
228
+ miniconda.sh
229
+ /database/python_sql/temp_data/
230
+ .history*
231
+ .history/
232
+ .history/
233
+
234
+ _dev/
235
+ /_dev/
236
+ requirements_filtered.txt
237
+
238
+ # matrx-dev-tools backups
239
+ .env-backups/
240
+ # Matrx Ship config (contains API key)
241
+ .matrx-ship.json
242
+
243
+ # Matrx config (contains API keys)
244
+ .matrx.json
245
+ .matrx-tools.conf
246
+
247
+ # Claude Code local worktrees and per-user settings
248
+ .claude/worktrees/
249
+ .claude/settings.local.json
250
+
251
+ # Append-only snapshots from matrx_utils.update_history (unbounded; do not commit)
252
+ common/utils/data_in_code/data_history.json
253
+ packages/matrx-utils/matrx_utils/data_in_code/data_history.json
254
+
255
+ # Tool-dispatch debug logs — one file per server start, never committed
256
+ .matrx-debug/
257
+
258
+ # macOS Finder metadata
259
+ .DS_Store
260
+ **/.DS_Store
261
+
262
+ # Environment files
263
+ .env
264
+ .env.*
265
+ *.env
266
+ *.env.*
267
+
268
+ # Keep safe templates trackable
269
+ !.env.example
270
+ !.env.sample
271
+ !.env.template
@@ -0,0 +1 @@
1
+ CLAUDE.md
@@ -0,0 +1,20 @@
1
+ # CLAUDE.md — matrx-assignment
2
+
3
+ `matrx-assignment` is the payload-agnostic assignment planner and coordinator. It
4
+ materializes variable values, tracks resumable item state through an injected
5
+ `AssignmentStore`, and calls a host-provided executor. It never imports an agent,
6
+ workflow, HTTP, database, cache, or host application.
7
+
8
+ Hard rules:
9
+
10
+ - Every known contract is a strict Pydantic model with `extra="forbid"`.
11
+ - Random choices use `secrets.SystemRandom`; pseudo-random seeds and time-derived
12
+ selection are forbidden.
13
+ - A plan is materialized exactly once. Retries consume persisted items and never
14
+ re-randomize them.
15
+ - Persistence is a protocol. Database implementations live in the host and must
16
+ use matrx-orm.
17
+ - The coordinator owns assignment lifecycle only. It does not execute agents or
18
+ workflows itself.
19
+
20
+ Run package tests with `uv run pytest packages/matrx-assignment/tests`.
@@ -0,0 +1,39 @@
1
+ # Coordinated assignment engine
2
+
3
+ `matrx-assignment` is the payload-blind planning and coordination library for
4
+ turning variable option sets into durable, executable items. It has no agent,
5
+ workflow, database, or HTTP dependency.
6
+
7
+ ## Contracts
8
+
9
+ - All known request, plan, item, claim, error, and result shapes are strict,
10
+ frozen Pydantic models (`extra="forbid"`).
11
+ - `coordinated_rows` preserves paired values such as `topic` + `research`.
12
+ - `independent_random` draws combinations with `secrets.SystemRandom`; it can
13
+ allow repeats or sample without replacement.
14
+ - `cartesian` enumerates every combination, in declared or randomized order.
15
+ - A plan is materialized exactly once. Recovery always reuses the persisted
16
+ materialization and never redraws random values.
17
+
18
+ ## Host seams
19
+
20
+ `AssignmentStore` is the only persistence protocol. `AssignmentCoordinator`
21
+ leases items with `SKIP LOCKED` semantics supplied by the host, heartbeats long
22
+ executions, retries retryable failures, reaps expired leases, and returns a
23
+ complete typed result. The injected executor receives a resolved value map and
24
+ can run an agent, image generator, or any future workload without changing the
25
+ engine.
26
+
27
+ ## Recovery and idempotency
28
+
29
+ The host supplies a logical session key. A retry with the same key resumes the
30
+ same session and deterministic item identities. Each item owns a deterministic
31
+ conversation identity, while attempts are separately audited. Provider calls
32
+ remain at-least-once at the unavoidable crash boundary between an external side
33
+ effect and its database acknowledgement; deterministic request identities make
34
+ that boundary reconcilable instead of invisible.
35
+
36
+ ## Change log
37
+
38
+ - **2026-07-18** — Initial strict planner, in-memory reference store, lease-based
39
+ coordinator, heartbeat/retry recovery, and standalone tests.
@@ -0,0 +1,30 @@
1
+ Metadata-Version: 2.4
2
+ Name: matrx-assignment
3
+ Version: 0.1.0
4
+ Summary: Typed planning and durable coordination for resolved variable assignments
5
+ Requires-Python: >=3.13
6
+ Requires-Dist: pydantic>=2.12
7
+ Provides-Extra: dev
8
+ Requires-Dist: pytest-asyncio>=0.25.0; extra == 'dev'
9
+ Requires-Dist: pytest>=8.3.0; extra == 'dev'
10
+ Requires-Dist: ruff>=0.9.0; extra == 'dev'
11
+ Description-Content-Type: text/markdown
12
+
13
+ # matrx-assignment
14
+
15
+ Typed, payload-agnostic assignment planning and durable coordination.
16
+
17
+ The library turns a strict plan into immutable work items, then drives those
18
+ items through an injected store and executor. Hosts can begin with coordinated
19
+ rows (for example, 50 paired `topic` + `research` inputs) and later add policies
20
+ without changing the agent or workflow execution systems.
21
+
22
+ Supported plans:
23
+
24
+ - `coordinated_rows`: preserve paired values and run every row once.
25
+ - `independent_random`: choose every variable independently with unbiased system
26
+ randomness, optionally without repeating a full combination.
27
+ - `cartesian`: enumerate combinations, optionally in unbiased random order.
28
+
29
+ Random plans are materialized once and persisted by the host. Recovery therefore
30
+ resumes the same items instead of producing a new draw.
@@ -0,0 +1,18 @@
1
+ # matrx-assignment
2
+
3
+ Typed, payload-agnostic assignment planning and durable coordination.
4
+
5
+ The library turns a strict plan into immutable work items, then drives those
6
+ items through an injected store and executor. Hosts can begin with coordinated
7
+ rows (for example, 50 paired `topic` + `research` inputs) and later add policies
8
+ without changing the agent or workflow execution systems.
9
+
10
+ Supported plans:
11
+
12
+ - `coordinated_rows`: preserve paired values and run every row once.
13
+ - `independent_random`: choose every variable independently with unbiased system
14
+ randomness, optionally without repeating a full combination.
15
+ - `cartesian`: enumerate combinations, optionally in unbiased random order.
16
+
17
+ Random plans are materialized once and persisted by the host. Recovery therefore
18
+ resumes the same items instead of producing a new draw.
@@ -0,0 +1,60 @@
1
+ from matrx_assignment.coordinator import (
2
+ AssignmentCoordinator,
3
+ AssignmentExecutionFailure,
4
+ AssignmentExecutor,
5
+ )
6
+ from matrx_assignment.in_memory import InMemoryAssignmentStore
7
+ from matrx_assignment.models import (
8
+ AssignmentBatchResult,
9
+ AssignmentClaim,
10
+ AssignmentError,
11
+ AssignmentExecutionOutput,
12
+ AssignmentItemResult,
13
+ AssignmentItemSnapshot,
14
+ AssignmentItemStatus,
15
+ AssignmentOrder,
16
+ AssignmentPlan,
17
+ AssignmentRow,
18
+ AssignmentSessionRequest,
19
+ AssignmentSessionStatus,
20
+ AssignmentSessionSummary,
21
+ AssignmentSource,
22
+ AssignmentUniqueness,
23
+ CartesianPlan,
24
+ CoordinatedRowsPlan,
25
+ IndependentRandomPlan,
26
+ MaterializedAssignment,
27
+ MaterializedPlan,
28
+ )
29
+ from matrx_assignment.planner import AssignmentPlanner, fingerprint_json
30
+ from matrx_assignment.store import AssignmentStore
31
+
32
+ __all__ = [
33
+ "AssignmentBatchResult",
34
+ "AssignmentClaim",
35
+ "AssignmentCoordinator",
36
+ "AssignmentError",
37
+ "AssignmentExecutionFailure",
38
+ "AssignmentExecutionOutput",
39
+ "AssignmentExecutor",
40
+ "AssignmentItemResult",
41
+ "AssignmentItemSnapshot",
42
+ "AssignmentItemStatus",
43
+ "AssignmentOrder",
44
+ "AssignmentPlan",
45
+ "AssignmentPlanner",
46
+ "AssignmentRow",
47
+ "AssignmentSessionRequest",
48
+ "AssignmentSessionStatus",
49
+ "AssignmentSessionSummary",
50
+ "AssignmentSource",
51
+ "AssignmentStore",
52
+ "AssignmentUniqueness",
53
+ "CartesianPlan",
54
+ "CoordinatedRowsPlan",
55
+ "IndependentRandomPlan",
56
+ "InMemoryAssignmentStore",
57
+ "MaterializedAssignment",
58
+ "MaterializedPlan",
59
+ "fingerprint_json",
60
+ ]
@@ -0,0 +1,189 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import socket
5
+ from collections.abc import Awaitable, Callable
6
+ from datetime import UTC, datetime, timedelta
7
+ from typing import Protocol
8
+
9
+ from matrx_assignment.models import (
10
+ AssignmentBatchResult,
11
+ AssignmentClaim,
12
+ AssignmentError,
13
+ AssignmentExecutionOutput,
14
+ AssignmentItemResult,
15
+ AssignmentSessionRequest,
16
+ AssignmentSessionStatus,
17
+ )
18
+ from matrx_assignment.store import AssignmentStore
19
+
20
+
21
+ class AssignmentExecutor(Protocol):
22
+ async def __call__(self, claim: AssignmentClaim) -> AssignmentExecutionOutput: ...
23
+
24
+
25
+ ProgressCallback = Callable[[int, int], Awaitable[None]]
26
+
27
+
28
+ class AssignmentExecutionFailure(RuntimeError):
29
+ def __init__(
30
+ self,
31
+ message: str,
32
+ *,
33
+ code: str = "assignment_execution_failed",
34
+ retryable: bool = True,
35
+ details: dict[str, object] | None = None,
36
+ ) -> None:
37
+ super().__init__(message)
38
+ self.error = AssignmentError(
39
+ code=code,
40
+ message=message,
41
+ retryable=retryable,
42
+ details=details or {},
43
+ )
44
+
45
+
46
+ class AssignmentCoordinator:
47
+ """Lease and execute persisted items without knowing what the payload does."""
48
+
49
+ def __init__(self, store: AssignmentStore, *, holder: str | None = None) -> None:
50
+ self._store = store
51
+ self._holder = holder or socket.gethostname()
52
+
53
+ async def run(
54
+ self,
55
+ session_id: str,
56
+ request: AssignmentSessionRequest,
57
+ executor: AssignmentExecutor,
58
+ *,
59
+ progress: ProgressCallback | None = None,
60
+ ) -> AssignmentBatchResult:
61
+ await self._store.reap_expired_claims(session_id, now=datetime.now(UTC))
62
+ while True:
63
+ session = await self._store.get_session(session_id)
64
+ if session.status in {
65
+ AssignmentSessionStatus.COMPLETED,
66
+ AssignmentSessionStatus.PARTIALLY_FAILED,
67
+ AssignmentSessionStatus.FAILED,
68
+ AssignmentSessionStatus.CANCELLED,
69
+ }:
70
+ break
71
+
72
+ claims = await self._store.claim_items(
73
+ session_id,
74
+ holder=self._holder,
75
+ limit=request.max_concurrency,
76
+ lease_seconds=request.lease_seconds,
77
+ )
78
+ if not claims:
79
+ session = await self._store.finalize_session(session_id)
80
+ if session.status is AssignmentSessionStatus.RUNNING:
81
+ await asyncio.sleep(
82
+ min(1.0, max(0.05, float(request.retry_delay_seconds)))
83
+ )
84
+ continue
85
+ break
86
+
87
+ await asyncio.gather(
88
+ *(
89
+ self._execute_one(
90
+ claim,
91
+ request=request,
92
+ executor=executor,
93
+ )
94
+ for claim in claims
95
+ )
96
+ )
97
+ if progress is not None:
98
+ snapshot = await self._store.get_session(session_id)
99
+ await progress(snapshot.completed_items + snapshot.failed_items, snapshot.total_items)
100
+
101
+ await self._store.finalize_session(session_id)
102
+ return await self.snapshot(session_id)
103
+
104
+ async def snapshot(self, session_id: str) -> AssignmentBatchResult:
105
+ """Load the durable result without claiming or executing any work."""
106
+
107
+ final_session = await self._store.get_session(session_id)
108
+ items = await self._store.list_items(session_id)
109
+ return AssignmentBatchResult(
110
+ session=final_session,
111
+ items=[
112
+ AssignmentItemResult(
113
+ id=item.id,
114
+ ordinal=item.ordinal,
115
+ key=item.key,
116
+ status=item.status,
117
+ values=item.values,
118
+ output=item.output,
119
+ output_kind=item.output_kind,
120
+ output_kind_version=item.output_kind_version,
121
+ conversation_id=item.conversation_id,
122
+ runtime_execution_id=item.runtime_execution_id,
123
+ error=item.error,
124
+ )
125
+ for item in items
126
+ ],
127
+ )
128
+
129
+ async def _execute_one(
130
+ self,
131
+ claim: AssignmentClaim,
132
+ *,
133
+ request: AssignmentSessionRequest,
134
+ executor: AssignmentExecutor,
135
+ ) -> None:
136
+ await self._store.mark_running(claim)
137
+ heartbeat_stop = asyncio.Event()
138
+ heartbeat = asyncio.create_task(
139
+ self._renew_lease(
140
+ claim,
141
+ lease_seconds=request.lease_seconds,
142
+ stop=heartbeat_stop,
143
+ )
144
+ )
145
+ try:
146
+ output = await executor(claim)
147
+ except AssignmentExecutionFailure as exc:
148
+ error = exc.error
149
+ except Exception as exc: # executor boundary: unknown failures are retryable by default
150
+ error = AssignmentError(
151
+ code=type(exc).__name__,
152
+ message=str(exc) or type(exc).__name__,
153
+ retryable=True,
154
+ )
155
+ else:
156
+ await self._store.complete_item(claim, output)
157
+ return
158
+ finally:
159
+ heartbeat_stop.set()
160
+ await heartbeat
161
+
162
+ retryable = error.retryable and claim.attempt_number < claim.item.max_attempts
163
+ if retryable != error.retryable:
164
+ error = error.model_copy(update={"retryable": False})
165
+ delay = request.retry_delay_seconds * (2 ** max(0, claim.attempt_number - 1))
166
+ await self._store.fail_item(
167
+ claim,
168
+ error,
169
+ retry_at=datetime.now(UTC) + timedelta(seconds=delay),
170
+ )
171
+
172
+ async def _renew_lease(
173
+ self,
174
+ claim: AssignmentClaim,
175
+ *,
176
+ lease_seconds: int,
177
+ stop: asyncio.Event,
178
+ ) -> None:
179
+ interval = max(10.0, lease_seconds / 3)
180
+ while True:
181
+ try:
182
+ await asyncio.wait_for(stop.wait(), timeout=interval)
183
+ return
184
+ except TimeoutError:
185
+ if not await self._store.renew_claim(
186
+ claim,
187
+ lease_seconds=lease_seconds,
188
+ ):
189
+ return