trellis-pipelines 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.
Files changed (60) hide show
  1. trellis_pipelines-0.1.0/LICENSE +21 -0
  2. trellis_pipelines-0.1.0/PKG-INFO +180 -0
  3. trellis_pipelines-0.1.0/README.md +109 -0
  4. trellis_pipelines-0.1.0/pyproject.toml +104 -0
  5. trellis_pipelines-0.1.0/scripts/benchmark.py +0 -0
  6. trellis_pipelines-0.1.0/setup.cfg +4 -0
  7. trellis_pipelines-0.1.0/trellis/__init__.py +0 -0
  8. trellis_pipelines-0.1.0/trellis/exceptions.py +102 -0
  9. trellis_pipelines-0.1.0/trellis/execution/__init__.py +0 -0
  10. trellis_pipelines-0.1.0/trellis/execution/blackboard.py +59 -0
  11. trellis_pipelines-0.1.0/trellis/execution/dag.py +610 -0
  12. trellis_pipelines-0.1.0/trellis/execution/orchestrator.py +318 -0
  13. trellis_pipelines-0.1.0/trellis/execution/prefect_adapter.py +39 -0
  14. trellis_pipelines-0.1.0/trellis/execution/run_queue.py +112 -0
  15. trellis_pipelines-0.1.0/trellis/execution/template.py +348 -0
  16. trellis_pipelines-0.1.0/trellis/models/__init__.py +6 -0
  17. trellis_pipelines-0.1.0/trellis/models/document.py +256 -0
  18. trellis_pipelines-0.1.0/trellis/models/handles.py +168 -0
  19. trellis_pipelines-0.1.0/trellis/models/pipeline.py +524 -0
  20. trellis_pipelines-0.1.0/trellis/models/plan.py +171 -0
  21. trellis_pipelines-0.1.0/trellis/registry/__init__.py +12 -0
  22. trellis_pipelines-0.1.0/trellis/registry/finance_functions.py +1126 -0
  23. trellis_pipelines-0.1.0/trellis/registry/functions.py +105 -0
  24. trellis_pipelines-0.1.0/trellis/registry/schema.py +54 -0
  25. trellis_pipelines-0.1.0/trellis/tools/__init__.py +12 -0
  26. trellis_pipelines-0.1.0/trellis/tools/base.py +129 -0
  27. trellis_pipelines-0.1.0/trellis/tools/decorators.py +203 -0
  28. trellis_pipelines-0.1.0/trellis/tools/impls/__init__.py +33 -0
  29. trellis_pipelines-0.1.0/trellis/tools/impls/compute.py +115 -0
  30. trellis_pipelines-0.1.0/trellis/tools/impls/document.py +827 -0
  31. trellis_pipelines-0.1.0/trellis/tools/impls/export.py +448 -0
  32. trellis_pipelines-0.1.0/trellis/tools/impls/extract.py +522 -0
  33. trellis_pipelines-0.1.0/trellis/tools/impls/extract_fields.py +393 -0
  34. trellis_pipelines-0.1.0/trellis/tools/impls/fetch.py +344 -0
  35. trellis_pipelines-0.1.0/trellis/tools/impls/llm.py +209 -0
  36. trellis_pipelines-0.1.0/trellis/tools/impls/load_schema.py +318 -0
  37. trellis_pipelines-0.1.0/trellis/tools/impls/mock.py +57 -0
  38. trellis_pipelines-0.1.0/trellis/tools/impls/search.py +187 -0
  39. trellis_pipelines-0.1.0/trellis/tools/impls/select.py +185 -0
  40. trellis_pipelines-0.1.0/trellis/tools/impls/store.py +36 -0
  41. trellis_pipelines-0.1.0/trellis/tools/registry.py +190 -0
  42. trellis_pipelines-0.1.0/trellis/validation/__init__.py +1 -0
  43. trellis_pipelines-0.1.0/trellis/validation/contract.py +304 -0
  44. trellis_pipelines-0.1.0/trellis/validation/graph.py +273 -0
  45. trellis_pipelines-0.1.0/trellis_api/__init__.py +7 -0
  46. trellis_pipelines-0.1.0/trellis_api/main.py +50 -0
  47. trellis_pipelines-0.1.0/trellis_api/routers/__init__.py +6 -0
  48. trellis_pipelines-0.1.0/trellis_api/routers/pipelines.py +224 -0
  49. trellis_pipelines-0.1.0/trellis_api/routers/plans.py +18 -0
  50. trellis_pipelines-0.1.0/trellis_api/schemas.py +88 -0
  51. trellis_pipelines-0.1.0/trellis_cli/__init__.py +5 -0
  52. trellis_pipelines-0.1.0/trellis_cli/main.py +549 -0
  53. trellis_pipelines-0.1.0/trellis_mcp/__init__.py +3 -0
  54. trellis_pipelines-0.1.0/trellis_mcp/server.py +0 -0
  55. trellis_pipelines-0.1.0/trellis_pipelines.egg-info/PKG-INFO +180 -0
  56. trellis_pipelines-0.1.0/trellis_pipelines.egg-info/SOURCES.txt +58 -0
  57. trellis_pipelines-0.1.0/trellis_pipelines.egg-info/dependency_links.txt +1 -0
  58. trellis_pipelines-0.1.0/trellis_pipelines.egg-info/entry_points.txt +2 -0
  59. trellis_pipelines-0.1.0/trellis_pipelines.egg-info/requires.txt +25 -0
  60. trellis_pipelines-0.1.0/trellis_pipelines.egg-info/top_level.txt +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ciaran Davies
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,180 @@
1
+ Metadata-Version: 2.4
2
+ Name: trellis-pipelines
3
+ Version: 0.1.0
4
+ Summary: Modular agentic pipeline system for document and data workflows
5
+ Author: Ciaran Davies
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Ciaran Davies
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/your-org/trellis
29
+ Project-URL: Repository, https://github.com/your-org/trellis
30
+ Project-URL: Documentation, https://your-org.github.io/trellis
31
+ Project-URL: Bug Tracker, https://github.com/your-org/trellis/issues
32
+ Keywords: pipeline,agentic,dag,document,extraction,llm,pdf,sec,etl,workflow
33
+ Classifier: Development Status :: 3 - Alpha
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: Intended Audience :: Science/Research
36
+ Classifier: License :: OSI Approved :: MIT License
37
+ Classifier: Operating System :: OS Independent
38
+ Classifier: Programming Language :: Python :: 3
39
+ Classifier: Programming Language :: Python :: 3.12
40
+ Classifier: Programming Language :: Python :: 3.13
41
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
42
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
43
+ Classifier: Topic :: Office/Business
44
+ Classifier: Typing :: Typed
45
+ Requires-Python: >=3.12
46
+ Description-Content-Type: text/markdown
47
+ License-File: LICENSE
48
+ Requires-Dist: pydantic>=2.0
49
+ Requires-Dist: pyyaml>=6.0
50
+ Requires-Dist: litellm>=1.0.0
51
+ Requires-Dist: PyMuPDF>=1.24.0
52
+ Requires-Dist: pypdf>=3.0.0
53
+ Requires-Dist: python-dotenv>=1.0.0
54
+ Requires-Dist: beautifulsoup4>=4.12.0
55
+ Requires-Dist: typer>=0.9
56
+ Provides-Extra: api
57
+ Requires-Dist: fastapi>=0.104; extra == "api"
58
+ Requires-Dist: uvicorn[standard]>=0.24; extra == "api"
59
+ Provides-Extra: dev
60
+ Requires-Dist: pytest>=7.0; extra == "dev"
61
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
62
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
63
+ Requires-Dist: httpx>=0.24; extra == "dev"
64
+ Requires-Dist: black>=23.0; extra == "dev"
65
+ Requires-Dist: isort>=5.12; extra == "dev"
66
+ Requires-Dist: mypy>=1.0; extra == "dev"
67
+ Requires-Dist: ruff>=0.1; extra == "dev"
68
+ Provides-Extra: all
69
+ Requires-Dist: trellis-pipelines[api]; extra == "all"
70
+ Dynamic: license-file
71
+
72
+ # Trellis
73
+
74
+ An agentic solution for pipeline planning and execution within controlled environments.
75
+
76
+ ## Quick Start
77
+
78
+ ### Installation
79
+
80
+ ```bash
81
+ pip install -e .
82
+ ```
83
+
84
+ ### Usage
85
+
86
+ #### CLI
87
+
88
+ ```bash
89
+ # Validate a pipeline
90
+ trellis validate pipelines/example.yaml
91
+
92
+ # Run a pipeline
93
+ trellis run pipelines/example.yaml
94
+
95
+ # Generate a plan from a goal
96
+ trellis plan "Extract and analyze market data"
97
+
98
+ # Generate a pipeline from a goal
99
+ trellis generate "Extract and analyze market data" -o output.yaml
100
+ ```
101
+
102
+ #### API
103
+
104
+ ```bash
105
+ # Start the API server
106
+ python -m trellis_api.main
107
+
108
+ # Create a pipeline
109
+ curl -X POST http://localhost:8000/pipelines \
110
+ -H "Content-Type: application/json" \
111
+ -d '{
112
+ "id": "example_pipeline",
113
+ "goal": "Process data",
114
+ "tasks": []
115
+ }'
116
+ ```
117
+
118
+ #### Python Library
119
+
120
+ ```python
121
+ from trellis.models.pipeline import Pipeline
122
+ from trellis.execution.dag import DAGExecutor
123
+
124
+ pipeline = Pipeline(
125
+ id="my_pipeline",
126
+ goal="Extract and process data",
127
+ tasks=[]
128
+ )
129
+
130
+ executor = DAGExecutor()
131
+ results = executor.execute({})
132
+ ```
133
+
134
+ ## Architecture
135
+
136
+ See [docs/architecture.md](docs/specifications/ARCHITECTURE.md) for detailed architecture documentation.
137
+
138
+ ## Project Structure
139
+
140
+ ```
141
+ trellis/
142
+ ├── trellis/ # Core library (shared)
143
+ │ ├── models/ # Pydantic DSL models
144
+ │ ├── validation/ # DAG and contract validation
145
+ │ ├── execution/ # Execution engine
146
+ │ └── tools/ # Tool protocol and implementations
147
+ ├── trelis_api/ # FastAPI server
148
+ ├── trelis_mcp/ # MCP server
149
+ ├── trelis_cli/ # CLI interface
150
+ ├── data/ # Dataset generation
151
+ ├── tests/ # Test suite
152
+ ├── docs/ # Documentation
153
+ └── scripts/ # Utility scripts
154
+ ```
155
+
156
+ ## Development
157
+
158
+ ### Testing
159
+
160
+ ```bash
161
+ pytest tests/ -v
162
+ ```
163
+
164
+ ### Linting
165
+
166
+ ```bash
167
+ black .
168
+ isort .
169
+ mypy trellis/
170
+ ```
171
+
172
+ ### Building
173
+
174
+ ```bash
175
+ python -m build
176
+ ```
177
+
178
+ ## License
179
+
180
+ See LICENSE file for details.
@@ -0,0 +1,109 @@
1
+ # Trellis
2
+
3
+ An agentic solution for pipeline planning and execution within controlled environments.
4
+
5
+ ## Quick Start
6
+
7
+ ### Installation
8
+
9
+ ```bash
10
+ pip install -e .
11
+ ```
12
+
13
+ ### Usage
14
+
15
+ #### CLI
16
+
17
+ ```bash
18
+ # Validate a pipeline
19
+ trellis validate pipelines/example.yaml
20
+
21
+ # Run a pipeline
22
+ trellis run pipelines/example.yaml
23
+
24
+ # Generate a plan from a goal
25
+ trellis plan "Extract and analyze market data"
26
+
27
+ # Generate a pipeline from a goal
28
+ trellis generate "Extract and analyze market data" -o output.yaml
29
+ ```
30
+
31
+ #### API
32
+
33
+ ```bash
34
+ # Start the API server
35
+ python -m trellis_api.main
36
+
37
+ # Create a pipeline
38
+ curl -X POST http://localhost:8000/pipelines \
39
+ -H "Content-Type: application/json" \
40
+ -d '{
41
+ "id": "example_pipeline",
42
+ "goal": "Process data",
43
+ "tasks": []
44
+ }'
45
+ ```
46
+
47
+ #### Python Library
48
+
49
+ ```python
50
+ from trellis.models.pipeline import Pipeline
51
+ from trellis.execution.dag import DAGExecutor
52
+
53
+ pipeline = Pipeline(
54
+ id="my_pipeline",
55
+ goal="Extract and process data",
56
+ tasks=[]
57
+ )
58
+
59
+ executor = DAGExecutor()
60
+ results = executor.execute({})
61
+ ```
62
+
63
+ ## Architecture
64
+
65
+ See [docs/architecture.md](docs/specifications/ARCHITECTURE.md) for detailed architecture documentation.
66
+
67
+ ## Project Structure
68
+
69
+ ```
70
+ trellis/
71
+ ├── trellis/ # Core library (shared)
72
+ │ ├── models/ # Pydantic DSL models
73
+ │ ├── validation/ # DAG and contract validation
74
+ │ ├── execution/ # Execution engine
75
+ │ └── tools/ # Tool protocol and implementations
76
+ ├── trelis_api/ # FastAPI server
77
+ ├── trelis_mcp/ # MCP server
78
+ ├── trelis_cli/ # CLI interface
79
+ ├── data/ # Dataset generation
80
+ ├── tests/ # Test suite
81
+ ├── docs/ # Documentation
82
+ └── scripts/ # Utility scripts
83
+ ```
84
+
85
+ ## Development
86
+
87
+ ### Testing
88
+
89
+ ```bash
90
+ pytest tests/ -v
91
+ ```
92
+
93
+ ### Linting
94
+
95
+ ```bash
96
+ black .
97
+ isort .
98
+ mypy trellis/
99
+ ```
100
+
101
+ ### Building
102
+
103
+ ```bash
104
+ python -m build
105
+ ```
106
+
107
+ ## License
108
+
109
+ See LICENSE file for details.
@@ -0,0 +1,104 @@
1
+ [build-system]
2
+ requires = ["setuptools>=65", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ # NOTE: "trellis" is already registered on PyPI (Phillip J. Eby, v0.7a2).
7
+ # Replace the name below with your chosen PyPI name before publishing.
8
+ # The import name ("trellis", "trellis_api", etc.) is unaffected by this field.
9
+ name = "trellis-pipelines"
10
+ version = "0.1.0"
11
+ description = "Modular agentic pipeline system for document and data workflows"
12
+ readme = "README.md"
13
+ license = {file = "LICENSE"}
14
+ authors = [
15
+ {name = "Ciaran Davies"},
16
+ ]
17
+ requires-python = ">=3.12"
18
+ keywords = [
19
+ "pipeline", "agentic", "dag", "document", "extraction",
20
+ "llm", "pdf", "sec", "etl", "workflow",
21
+ ]
22
+ classifiers = [
23
+ "Development Status :: 3 - Alpha",
24
+ "Intended Audience :: Developers",
25
+ "Intended Audience :: Science/Research",
26
+ "License :: OSI Approved :: MIT License",
27
+ "Operating System :: OS Independent",
28
+ "Programming Language :: Python :: 3",
29
+ "Programming Language :: Python :: 3.12",
30
+ "Programming Language :: Python :: 3.13",
31
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
32
+ "Topic :: Software Development :: Libraries :: Python Modules",
33
+ "Topic :: Office/Business",
34
+ "Typing :: Typed",
35
+ ]
36
+ # ── Core dependencies ────────────────────────────────────────────────────────
37
+ # Installed for every user. FastAPI/uvicorn are optional (see [api] extra).
38
+ dependencies = [
39
+ "pydantic>=2.0",
40
+ "pyyaml>=6.0",
41
+ "litellm>=1.0.0",
42
+ "PyMuPDF>=1.24.0",
43
+ "pypdf>=3.0.0",
44
+ "python-dotenv>=1.0.0",
45
+ "beautifulsoup4>=4.12.0",
46
+ "typer>=0.9",
47
+ ]
48
+
49
+ [project.urls]
50
+ Homepage = "https://github.com/your-org/trellis"
51
+ Repository = "https://github.com/your-org/trellis"
52
+ Documentation = "https://your-org.github.io/trellis"
53
+ "Bug Tracker" = "https://github.com/your-org/trellis/issues"
54
+
55
+ # ── Optional extras ──────────────────────────────────────────────────────────
56
+ [project.optional-dependencies]
57
+ api = [
58
+ "fastapi>=0.104",
59
+ "uvicorn[standard]>=0.24",
60
+ ]
61
+ dev = [
62
+ "pytest>=7.0",
63
+ "pytest-cov>=4.0",
64
+ "pytest-asyncio>=0.21.0",
65
+ "httpx>=0.24",
66
+ "black>=23.0",
67
+ "isort>=5.12",
68
+ "mypy>=1.0",
69
+ "ruff>=0.1",
70
+ ]
71
+ all = [
72
+ "trellis-pipelines[api]",
73
+ ]
74
+
75
+ [project.scripts]
76
+ trellis = "trellis_cli.main:main"
77
+
78
+ # ── Package discovery ────────────────────────────────────────────────────────
79
+ # find: auto-discovers all packages (any directory with __init__.py).
80
+ # tests and build artefacts are excluded from the distribution.
81
+ [tool.setuptools.packages.find]
82
+ where = ["."]
83
+ include = ["trellis*", "trellis_api*", "trellis_cli*", "trellis_mcp*"]
84
+
85
+ # ── Tool configuration ───────────────────────────────────────────────────────
86
+ [tool.black]
87
+ line-length = 100
88
+
89
+ [tool.isort]
90
+ profile = "black"
91
+ line_length = 100
92
+
93
+ [tool.ruff]
94
+ line-length = 100
95
+
96
+ [tool.mypy]
97
+ python_version = "3.12"
98
+ warn_return_any = true
99
+ warn_unused_configs = true
100
+ ignore_missing_imports = true
101
+
102
+ [tool.pytest.ini_options]
103
+ testpaths = ["tests"]
104
+ asyncio_mode = "auto"
File without changes
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,102 @@
1
+ """
2
+ trellis.exceptions — Shared exception hierarchy for the Trellis runtime.
3
+
4
+ All public-facing errors inherit from TrellisError so callers can catch
5
+ broadly or narrowly as needed.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+
11
+ class TrellisError(Exception):
12
+ """Base class for all Trellis errors."""
13
+
14
+
15
+ # ---------------------------------------------------------------------------
16
+ # Validation errors
17
+ # ---------------------------------------------------------------------------
18
+
19
+
20
+ class ValidationError(TrellisError):
21
+ """Base class for all validation failures."""
22
+
23
+
24
+ class CycleError(ValidationError):
25
+ """
26
+ Raised when a cycle is detected in a task DAG or a plan sub-pipeline DAG.
27
+
28
+ Attributes:
29
+ cycle: The sequence of node IDs forming the cycle, if recoverable.
30
+ May be None if only the presence of a cycle is known.
31
+ """
32
+
33
+ def __init__(self, message: str, cycle: list[str] | None = None) -> None:
34
+ super().__init__(message)
35
+ self.cycle = cycle
36
+
37
+
38
+ class UnresolvedRefError(ValidationError):
39
+ """
40
+ Raised when a template expression references a task or key that does
41
+ not exist in the current scope.
42
+ """
43
+
44
+
45
+ class ContractError(ValidationError):
46
+ """
47
+ Raised when a pipeline's store tasks do not match the sub-pipeline's
48
+ stores declaration in the plan, or when a session reference is made
49
+ to a key not listed in reads.
50
+ """
51
+
52
+
53
+ class UnknownToolError(ValidationError):
54
+ """Raised when a task references a tool not present in the registry."""
55
+
56
+
57
+ class PipelineParamError(ValidationError):
58
+ """
59
+ Raised when pipeline parameter validation fails at invocation time —
60
+ e.g. a required param is missing or a value cannot be coerced to the
61
+ declared type.
62
+ """
63
+
64
+
65
+ # ---------------------------------------------------------------------------
66
+ # Execution errors
67
+ # ---------------------------------------------------------------------------
68
+
69
+
70
+ class ExecutionError(TrellisError):
71
+ """Base class for runtime execution failures."""
72
+
73
+
74
+ class ResolutionError(ExecutionError):
75
+ """
76
+ Raised when a template expression cannot be resolved against the current
77
+ execution context — e.g. a task output that does not exist yet, a
78
+ field path that does not match the output structure, or an unknown
79
+ namespace.
80
+ """
81
+
82
+
83
+ class TaskFailedError(ExecutionError):
84
+ """
85
+ Raised when a task fails after exhausting all retry attempts.
86
+
87
+ Attributes:
88
+ task_id: The id of the failing task.
89
+ cause: The underlying exception that caused the failure.
90
+ """
91
+
92
+ def __init__(self, task_id: str, cause: Exception) -> None:
93
+ super().__init__(f"Task {task_id!r} failed: {cause}")
94
+ self.task_id = task_id
95
+ self.cause = cause
96
+
97
+
98
+ class BlackboardKeyError(ExecutionError):
99
+ """
100
+ Raised when a session blackboard read is attempted for a key that does
101
+ not exist and no default is provided.
102
+ """
File without changes
@@ -0,0 +1,59 @@
1
+ """
2
+ Simple tenant-scoped blackboard for session persistence.
3
+
4
+ This module provides a minimal in-memory implementation suitable for local
5
+ runs and testing. The interface is intentionally tiny so it can be swapped for
6
+ Redis/Postgres/Prefect Blocks later without touching the executor.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass, field
11
+ from typing import Any, Dict, Iterable
12
+
13
+
14
+ class Blackboard:
15
+ """Abstract tenant-scoped key/value store."""
16
+
17
+ def get_all(self, tenant_id: str) -> Dict[str, Any]: # pragma: no cover - interface
18
+ raise NotImplementedError
19
+
20
+ def read_many(self, tenant_id: str, keys: Iterable[str]) -> Dict[str, Any]: # pragma: no cover - interface
21
+ raise NotImplementedError
22
+
23
+ def write(self, tenant_id: str, key: str, value: Any, *, append: bool = False) -> None: # pragma: no cover - interface
24
+ raise NotImplementedError
25
+
26
+
27
+ @dataclass
28
+ class InMemoryBlackboard(Blackboard):
29
+ """Naive in-memory implementation.
30
+
31
+ Data layout: { tenant_id -> { key -> value } }
32
+ """
33
+
34
+ _data: Dict[str, Dict[str, Any]] = field(default_factory=dict)
35
+
36
+ def _tenant(self, tenant_id: str) -> Dict[str, Any]:
37
+ return self._data.setdefault(tenant_id, {})
38
+
39
+ def get_all(self, tenant_id: str) -> Dict[str, Any]:
40
+ return dict(self._tenant(tenant_id))
41
+
42
+ def read_many(self, tenant_id: str, keys: Iterable[str]) -> Dict[str, Any]:
43
+ t = self._tenant(tenant_id)
44
+ return {k: t[k] for k in keys if k in t}
45
+
46
+ def write(self, tenant_id: str, key: str, value: Any, *, append: bool = False) -> None:
47
+ t = self._tenant(tenant_id)
48
+ if append:
49
+ if key not in t:
50
+ t[key] = [value]
51
+ else:
52
+ existing = t[key]
53
+ if isinstance(existing, list):
54
+ existing.append(value)
55
+ else:
56
+ t[key] = [existing, value]
57
+ else:
58
+ t[key] = value
59
+