relay-middleware 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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kriday Dave
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,198 @@
1
+ Metadata-Version: 2.4
2
+ Name: relay-middleware
3
+ Version: 0.1.0
4
+ Summary: A context-driven data pipeline and snapshotting library
5
+ Author: Kriday Dave
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Kriday Dave
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/kridaydave/relay
29
+ Project-URL: Bug Tracker, https://github.com/kridaydave/relay/issues
30
+ Classifier: Programming Language :: Python :: 3
31
+ Classifier: Programming Language :: Python :: 3.11
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Classifier: Operating System :: OS Independent
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
36
+ Requires-Python: >=3.11
37
+ Description-Content-Type: text/markdown
38
+ License-File: LICENSE
39
+ Requires-Dist: pytest
40
+ Requires-Dist: mypy
41
+ Requires-Dist: pydantic
42
+ Dynamic: license-file
43
+
44
+ # Relay
45
+
46
+ **Agent-agent context passing, done right.**
47
+
48
+ Relay is a lightweight, open source Python middleware library for passing context reliably between AI agents in a multi-agent pipeline. Works with any LLM provider or framework — LangChain, OpenAI, Anthropic, LiteLLM, or your own agents.
49
+
50
+ ---
51
+
52
+ ## The Problem
53
+
54
+ One hallucinating agent silently corrupts the shared context, and every downstream agent inherits the damage. Existing orchestration tools treat the context window as a mutable blob with no version control.
55
+
56
+ ## The Solution
57
+
58
+ Relay treats context like a ledger: append-only, signed at every step, and reversible.
59
+
60
+ ---
61
+
62
+ ## Features
63
+
64
+ - **Context Broker** — Normalizes, timestamps, and cryptographically signs context envelopes
65
+ - **Handoff Validator** — Detects contradictions and triggers rollback on corruption
66
+ - **Snapshot Store** — Persists immutable checkpoints for automatic rollback
67
+
68
+ ---
69
+
70
+ ## Installation
71
+
72
+ ```bash
73
+ git clone https://github.com/kridaydave/Relay.git
74
+ cd Relay
75
+ pip install -e .
76
+ ```
77
+
78
+ ---
79
+
80
+ ## The Aha Moment
81
+
82
+ **Without Relay** (manual, error-prone):
83
+
84
+ ```python
85
+ # Agent 1 produces output
86
+ agent1_output = {"entities": ["Apple", "2024 revenue"], "summary": "Apple grew"}
87
+
88
+ # Manual serialization — easy to lose data, corrupt context
89
+ context = json.dumps(agent1_output)
90
+
91
+ # Agent 2 receives corrupted context
92
+ agent2_input = f"Given: {context}\nAnalyze this."
93
+ ```
94
+
95
+ **With Relay** (automatic, verified):
96
+
97
+ ```python
98
+ from relay.pipeline import RelayPipeline
99
+
100
+ pipeline = RelayPipeline(
101
+ signing_secret="your-secret-key",
102
+ token_budget=8000
103
+ )
104
+
105
+ # Agent 1 — creates signed envelope
106
+ result = pipeline.execute_step({"entities": ["Apple"], "revenue": "2024"})
107
+ envelope1 = result.value # signed, immutable
108
+
109
+ # Agent 2 — validator detects contradiction
110
+ # If Agent 2 accidentally drops "entities", rollback triggers automatically
111
+ result = pipeline.execute_step({"summary": "growth"}) # contradiction!
112
+ ```
113
+
114
+ **What happens on contradiction:**
115
+
116
+ ```python
117
+ # Validator detects: critical key "entities" disappeared
118
+ # Relay automatically rolls back to last clean snapshot
119
+
120
+ result = pipeline.rollback()
121
+ restored_envelope = result.value
122
+ # Now you have the clean envelope from step 1
123
+ ```
124
+
125
+ ---
126
+
127
+ ## How It Works
128
+
129
+ ```
130
+ Agent 1 → [Sign Envelope] → Agent 2 → [Validate] → Agent 3
131
+
132
+ [Snapshot]
133
+
134
+ [Rollback if dirty]
135
+ ```
136
+
137
+ Every handoff is signed and validated. If corruption is detected, Relay silently rolls back to the last clean checkpoint.
138
+
139
+ ---
140
+
141
+ ## Context Envelope
142
+
143
+ Every context move between agents is wrapped in a signed, immutable envelope:
144
+
145
+ ```python
146
+ {
147
+ "relay_version": "0.1.0",
148
+ "pipeline_id": "uuid-v4",
149
+ "step": 2,
150
+ "timestamp": "2026-05-04T10:22:00Z",
151
+ "token_budget_used": 1840,
152
+ "token_budget_total": 8000,
153
+ "payload": {...},
154
+ "signature": "sha256:abc123..."
155
+ }
156
+ ```
157
+
158
+ ---
159
+
160
+ ## Error Handling
161
+
162
+ Relay uses Result types instead of exceptions:
163
+
164
+ ```python
165
+ from relay.types import Success, Failure, Result
166
+
167
+ result = pipeline.execute_step({"task": "work"})
168
+ if isinstance(result, Success):
169
+ envelope = result.value
170
+ elif isinstance(result, Failure):
171
+ print(f"Error: {result.reason} (code: {result.code})")
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Testing
177
+
178
+ ```bash
179
+ pytest tests/unit -v
180
+ ```
181
+
182
+ Quality gates:
183
+ - mypy --strict passes
184
+ - >80% test coverage
185
+ - Every public function has a test
186
+
187
+ ---
188
+
189
+ ## License
190
+
191
+ MIT License - see LICENSE file
192
+
193
+ ---
194
+
195
+ ## Resources
196
+
197
+ - [Design Document](docs/Relay%20Design%20Document.txt)
198
+ - [Coding Rules](docs/Relay%20Coding%20Rules.txt)
@@ -0,0 +1,155 @@
1
+ # Relay
2
+
3
+ **Agent-agent context passing, done right.**
4
+
5
+ Relay is a lightweight, open source Python middleware library for passing context reliably between AI agents in a multi-agent pipeline. Works with any LLM provider or framework — LangChain, OpenAI, Anthropic, LiteLLM, or your own agents.
6
+
7
+ ---
8
+
9
+ ## The Problem
10
+
11
+ One hallucinating agent silently corrupts the shared context, and every downstream agent inherits the damage. Existing orchestration tools treat the context window as a mutable blob with no version control.
12
+
13
+ ## The Solution
14
+
15
+ Relay treats context like a ledger: append-only, signed at every step, and reversible.
16
+
17
+ ---
18
+
19
+ ## Features
20
+
21
+ - **Context Broker** — Normalizes, timestamps, and cryptographically signs context envelopes
22
+ - **Handoff Validator** — Detects contradictions and triggers rollback on corruption
23
+ - **Snapshot Store** — Persists immutable checkpoints for automatic rollback
24
+
25
+ ---
26
+
27
+ ## Installation
28
+
29
+ ```bash
30
+ git clone https://github.com/kridaydave/Relay.git
31
+ cd Relay
32
+ pip install -e .
33
+ ```
34
+
35
+ ---
36
+
37
+ ## The Aha Moment
38
+
39
+ **Without Relay** (manual, error-prone):
40
+
41
+ ```python
42
+ # Agent 1 produces output
43
+ agent1_output = {"entities": ["Apple", "2024 revenue"], "summary": "Apple grew"}
44
+
45
+ # Manual serialization — easy to lose data, corrupt context
46
+ context = json.dumps(agent1_output)
47
+
48
+ # Agent 2 receives corrupted context
49
+ agent2_input = f"Given: {context}\nAnalyze this."
50
+ ```
51
+
52
+ **With Relay** (automatic, verified):
53
+
54
+ ```python
55
+ from relay.pipeline import RelayPipeline
56
+
57
+ pipeline = RelayPipeline(
58
+ signing_secret="your-secret-key",
59
+ token_budget=8000
60
+ )
61
+
62
+ # Agent 1 — creates signed envelope
63
+ result = pipeline.execute_step({"entities": ["Apple"], "revenue": "2024"})
64
+ envelope1 = result.value # signed, immutable
65
+
66
+ # Agent 2 — validator detects contradiction
67
+ # If Agent 2 accidentally drops "entities", rollback triggers automatically
68
+ result = pipeline.execute_step({"summary": "growth"}) # contradiction!
69
+ ```
70
+
71
+ **What happens on contradiction:**
72
+
73
+ ```python
74
+ # Validator detects: critical key "entities" disappeared
75
+ # Relay automatically rolls back to last clean snapshot
76
+
77
+ result = pipeline.rollback()
78
+ restored_envelope = result.value
79
+ # Now you have the clean envelope from step 1
80
+ ```
81
+
82
+ ---
83
+
84
+ ## How It Works
85
+
86
+ ```
87
+ Agent 1 → [Sign Envelope] → Agent 2 → [Validate] → Agent 3
88
+
89
+ [Snapshot]
90
+
91
+ [Rollback if dirty]
92
+ ```
93
+
94
+ Every handoff is signed and validated. If corruption is detected, Relay silently rolls back to the last clean checkpoint.
95
+
96
+ ---
97
+
98
+ ## Context Envelope
99
+
100
+ Every context move between agents is wrapped in a signed, immutable envelope:
101
+
102
+ ```python
103
+ {
104
+ "relay_version": "0.1.0",
105
+ "pipeline_id": "uuid-v4",
106
+ "step": 2,
107
+ "timestamp": "2026-05-04T10:22:00Z",
108
+ "token_budget_used": 1840,
109
+ "token_budget_total": 8000,
110
+ "payload": {...},
111
+ "signature": "sha256:abc123..."
112
+ }
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Error Handling
118
+
119
+ Relay uses Result types instead of exceptions:
120
+
121
+ ```python
122
+ from relay.types import Success, Failure, Result
123
+
124
+ result = pipeline.execute_step({"task": "work"})
125
+ if isinstance(result, Success):
126
+ envelope = result.value
127
+ elif isinstance(result, Failure):
128
+ print(f"Error: {result.reason} (code: {result.code})")
129
+ ```
130
+
131
+ ---
132
+
133
+ ## Testing
134
+
135
+ ```bash
136
+ pytest tests/unit -v
137
+ ```
138
+
139
+ Quality gates:
140
+ - mypy --strict passes
141
+ - >80% test coverage
142
+ - Every public function has a test
143
+
144
+ ---
145
+
146
+ ## License
147
+
148
+ MIT License - see LICENSE file
149
+
150
+ ---
151
+
152
+ ## Resources
153
+
154
+ - [Design Document](docs/Relay%20Design%20Document.txt)
155
+ - [Coding Rules](docs/Relay%20Coding%20Rules.txt)
@@ -0,0 +1,57 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "relay-middleware"
7
+ version = "0.1.0"
8
+ description = "A context-driven data pipeline and snapshotting library"
9
+ readme = "README.md"
10
+ authors = [
11
+ { name="Kriday Dave" }
12
+ ]
13
+ license = { file="LICENSE" }
14
+ requires-python = ">=3.11"
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.11",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: OS Independent",
20
+ "Intended Audience :: Developers",
21
+ "Topic :: Software Development :: Libraries :: Python Modules",
22
+ ]
23
+ dependencies = [
24
+ "pytest",
25
+ "mypy",
26
+ "pydantic",
27
+ ]
28
+
29
+ [project.urls]
30
+ "Homepage" = "https://github.com/kridaydave/relay"
31
+ "Bug Tracker" = "https://github.com/kridaydave/relay/issues"
32
+
33
+ [tool.setuptools.packages.find]
34
+ where = ["src"]
35
+
36
+ [tool.pytest.ini_options]
37
+ testpaths = ["tests"]
38
+ python_files = ["test_*.py"]
39
+ python_classes = ["Test*"]
40
+ python_functions = ["test_*"]
41
+ addopts = "-v --tb=short"
42
+
43
+ [tool.coverage.run]
44
+ source = ["."]
45
+ omit = ["tests/*", "*/tests/*"]
46
+ branch = true
47
+
48
+ [tool.coverage.report]
49
+ precision = 2
50
+ show_missing = true
51
+ skip_covered = false
52
+
53
+ [tool.mypy]
54
+ python_version = "3.11"
55
+ warn_return_any = true
56
+ warn_unused_configs = true
57
+ disallow_untyped_defs = false
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ """Relay - A Python project."""
@@ -0,0 +1,55 @@
1
+ """Context envelope creation, signing, and lifecycle management for Relay.
2
+
3
+ Owns: envelope lifecycle, cryptographic signing.
4
+ Does NOT: validate agent output, persist snapshots, execute agents.
5
+ """
6
+
7
+ from dataclasses import dataclass
8
+ from typing import Any
9
+
10
+ from relay.envelope import ContextEnvelope, create_initial_envelope, create_next_envelope
11
+ from relay.types import Result, Success, Failure
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class ContextBroker:
16
+ """Manages context envelope creation and signing.
17
+
18
+ Owns: envelope lifecycle, cryptographic signing.
19
+ Does NOT: validate agent output, persist snapshots, execute agents.
20
+ """
21
+ signing_secret: str
22
+ token_budget_total: int
23
+
24
+ def create_initial_envelope(
25
+ self,
26
+ pipeline_id: str,
27
+ initial_payload: dict[str, Any]
28
+ ) -> Result[ContextEnvelope]:
29
+ """Create the first envelope for a pipeline."""
30
+ if not pipeline_id:
31
+ return Failure(reason="pipeline_id cannot be empty", code="INVALID_PIPELINE_ID")
32
+ if not initial_payload:
33
+ return Failure(reason="initial_payload cannot be empty", code="INVALID_PAYLOAD")
34
+
35
+ return create_initial_envelope(
36
+ pipeline_id=pipeline_id,
37
+ initial_payload=initial_payload,
38
+ token_budget_total=self.token_budget_total,
39
+ secret=self.signing_secret
40
+ )
41
+
42
+ def create_next_envelope(
43
+ self,
44
+ previous_envelope: ContextEnvelope,
45
+ agent_output: dict[str, Any]
46
+ ) -> Result[ContextEnvelope]:
47
+ """Create a subsequent envelope for the next step."""
48
+ if not agent_output:
49
+ return Failure(reason="agent_output cannot be empty", code="INVALID_PAYLOAD")
50
+
51
+ return create_next_envelope(
52
+ previous_envelope=previous_envelope,
53
+ agent_output=agent_output,
54
+ secret=self.signing_secret
55
+ )
@@ -0,0 +1,152 @@
1
+ """Core pipeline orchestration for Relay v0.1.
2
+
3
+ Owns: pipeline lifecycle, component coordination.
4
+ Does NOT: define agent behavior, manage prompts.
5
+ """
6
+
7
+ from dataclasses import dataclass, field
8
+ from typing import Any
9
+ import uuid
10
+
11
+ from relay.context_broker import ContextBroker
12
+ from relay.envelope import ContextEnvelope
13
+ from relay.snapshot import SnapshotStore
14
+ from relay.types import Failure, Result, Success, RollbackSuccess
15
+ from relay.validator import HandoffValidator, ValidationResult
16
+
17
+
18
+ @dataclass
19
+ class CoreRelayPipeline:
20
+ """Base class for pipeline orchestration.
21
+
22
+ Owns: pipeline lifecycle, component coordination.
23
+ Does NOT: define agent behavior, manage prompts.
24
+ """
25
+ signing_secret: str
26
+ token_budget: int = 8000
27
+ storage_path: str = "./relay_data/snapshots"
28
+
29
+ _pipeline_id: str = field(init=False, repr=False)
30
+ _context_broker: ContextBroker = field(init=False, repr=False)
31
+ _handoff_validator: HandoffValidator = field(init=False, repr=False)
32
+ _snapshot_store: SnapshotStore = field(init=False, repr=False)
33
+ _current_envelope: ContextEnvelope | None = field(default=None, init=False, repr=False)
34
+ _previous_envelopes: list[ContextEnvelope] = field(default_factory=list, init=False, repr=False)
35
+ _snapshot_ids: dict[str, str] = field(default_factory=dict, init=False, repr=False)
36
+
37
+ def __post_init__(self) -> None:
38
+ self._pipeline_id = uuid.uuid4().hex
39
+ self._context_broker = ContextBroker(
40
+ signing_secret=self.signing_secret,
41
+ token_budget_total=self.token_budget
42
+ )
43
+ self._handoff_validator = HandoffValidator()
44
+ self._snapshot_store = SnapshotStore(storage_path=self.storage_path)
45
+
46
+ def execute_step(self, agent_output: dict[str, Any]) -> Result[ContextEnvelope]:
47
+ """Execute a pipeline step with agent output."""
48
+ if self._current_envelope is None:
49
+ envelope_result = self._context_broker.create_initial_envelope(
50
+ pipeline_id=self._pipeline_id,
51
+ initial_payload=agent_output
52
+ )
53
+ if isinstance(envelope_result, Failure):
54
+ return envelope_result
55
+
56
+ new_envelope = envelope_result.value
57
+ self._current_envelope = new_envelope
58
+ return Success(new_envelope)
59
+
60
+ envelope_result = self._context_broker.create_next_envelope(
61
+ previous_envelope=self._current_envelope,
62
+ agent_output=agent_output
63
+ )
64
+ if isinstance(envelope_result, Failure):
65
+ return envelope_result
66
+
67
+ new_envelope = envelope_result.value
68
+
69
+ self._previous_envelopes.append(self._current_envelope)
70
+
71
+ current_snapshot_result = self._snapshot_store.save_snapshot(self._current_envelope)
72
+ if isinstance(current_snapshot_result, Failure):
73
+ return current_snapshot_result
74
+ self._snapshot_ids[self._current_envelope.step] = current_snapshot_result.value
75
+
76
+ validation_result = self._handoff_validator.validate_handoff(
77
+ previous_envelope=self._current_envelope,
78
+ current_envelope=new_envelope
79
+ )
80
+ if isinstance(validation_result, Failure):
81
+ return validation_result
82
+
83
+ validation = validation_result.value
84
+ if self._handoff_validator.should_rollback(validation):
85
+ return self._rollback_to_previous(new_envelope, validation)
86
+
87
+ snapshot_result = self._snapshot_store.save_snapshot(new_envelope)
88
+ if isinstance(snapshot_result, Failure):
89
+ return snapshot_result
90
+
91
+ snapshot_id = snapshot_result.value
92
+ self._snapshot_ids[new_envelope.step] = snapshot_id
93
+ if self._previous_envelopes:
94
+ oldest_step = self._previous_envelopes[0].step
95
+ self._snapshot_ids.pop(oldest_step, None)
96
+
97
+ self._current_envelope = new_envelope
98
+ return Success(new_envelope)
99
+
100
+ def _rollback_to_previous(
101
+ self,
102
+ proposed_envelope: ContextEnvelope,
103
+ validation: ValidationResult
104
+ ) -> Result[ContextEnvelope]:
105
+ """Rollback to previous envelope on contradiction."""
106
+ reason = validation.contradiction_details or "Contradiction detected"
107
+ return self._rollback_to_previous_with_reason(proposed_envelope, reason)
108
+
109
+ def _rollback_to_previous_with_reason(
110
+ self,
111
+ proposed_envelope: ContextEnvelope,
112
+ reason: str
113
+ ) -> Result[ContextEnvelope]:
114
+ """Rollback to previous envelope with explicit reason."""
115
+ if not self._previous_envelopes:
116
+ return Failure(reason="No previous envelope to rollback to", code="NO_ROLLBACK_AVAILABLE")
117
+
118
+ previous_envelope = self._previous_envelopes[-1]
119
+ snapshot_id = self._snapshot_ids.get(previous_envelope.step)
120
+ if snapshot_id is None:
121
+ return Failure(reason="No snapshot registered for step", code="NO_SNAPSHOT_REGISTERED")
122
+
123
+ restore_result = self._snapshot_store.load_snapshot(snapshot_id)
124
+ if isinstance(restore_result, Failure):
125
+ return restore_result
126
+
127
+ restored_envelope = restore_result.value
128
+ self._current_envelope = restored_envelope
129
+ return RollbackSuccess(value=restored_envelope, reason=reason)
130
+
131
+ def rollback(self) -> Result[ContextEnvelope]:
132
+ """Rollback to the last clean state."""
133
+ if not self._previous_envelopes:
134
+ return Failure(reason="No previous envelope to rollback to", code="NO_ROLLBACK_AVAILABLE")
135
+
136
+ previous_envelope = self._previous_envelopes[-1]
137
+ snapshot_id = self._snapshot_ids.get(previous_envelope.step)
138
+ if snapshot_id is None:
139
+ return Failure(reason="No snapshot registered for step", code="NO_SNAPSHOT_REGISTERED")
140
+
141
+ restore_result = self._snapshot_store.load_snapshot(snapshot_id)
142
+ if isinstance(restore_result, Failure):
143
+ return restore_result
144
+
145
+ self._previous_envelopes.pop()
146
+ restored_envelope = restore_result.value
147
+ self._current_envelope = restored_envelope
148
+ return RollbackSuccess(value=restored_envelope, reason="Manual rollback")
149
+
150
+ def get_current_envelope(self) -> ContextEnvelope | None:
151
+ """Get the current envelope."""
152
+ return self._current_envelope