nemoir-runtime 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,5 @@
1
+ .venv
2
+ .pytest_cache
3
+ .ruff_cache
4
+ *.egg-info
5
+ __pycache__
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 NemoIR
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,99 @@
1
+ Metadata-Version: 2.4
2
+ Name: nemoir-runtime
3
+ Version: 0.1.0
4
+ Summary: Python runtime core for NemoIR — execute compiled agent workflows as structured state machines with tool orchestration, policy enforcement, model-backed stage execution, and live event streaming.
5
+ Project-URL: Repository, https://github.com/nemoir
6
+ Author: NemoIR Contributors
7
+ License: MIT License
8
+
9
+ Copyright (c) 2025 NemoIR
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+ License-File: LICENSE
29
+ Keywords: agent,ai,compiler,llm,nemo,runtime,workflow
30
+ Classifier: Development Status :: 3 - Alpha
31
+ Classifier: Intended Audience :: Developers
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Classifier: Programming Language :: Python :: 3
34
+ Classifier: Programming Language :: Python :: 3.11
35
+ Classifier: Programming Language :: Python :: 3.12
36
+ Classifier: Programming Language :: Python :: 3.13
37
+ Classifier: Programming Language :: Python :: 3.14
38
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
39
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
40
+ Requires-Python: >=3.11
41
+ Requires-Dist: litellm>=1.0.0
42
+ Provides-Extra: dev
43
+ Requires-Dist: pyright>=1.1.0; extra == 'dev'
44
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
45
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
46
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
47
+ Description-Content-Type: text/markdown
48
+
49
+ # NemoIR Runtime
50
+
51
+ Python runtime core for [NemoIR](https://github.com/nemoir) — an LLVM-inspired compiler stack for agentic workflows.
52
+
53
+ Executes compiled agent workflows as structured state machines with tool orchestration, policy enforcement, model-backed stage execution (via [LiteLLM](https://github.com/BerriAI/litellm)), and live event streaming.
54
+
55
+ ## Features
56
+
57
+ - **Workflow runtime** — state-machine execution with stage ordering, read/write resolution, transition selection, and run limits.
58
+ - **Tool framework** — capability-based tool registration, catalog-driven parameter validation, and policy-gated invocation (`fs.read`, `fs.write`, `user.confirm`, `os.shell`, `user.elicit`).
59
+ - **Policy engine** — deny and before-policies with expression evaluation (e.g., path containment guards).
60
+ - **Model integration** — `ModelStageExecutor` with LiteLLM adapter, structured output enforcement, tool-call loop, `ModelRouter` for per-stage model routing, and optional streaming via `ModelStreamingAdapter`.
61
+ - **Live event streaming** — `WorkflowRuntime.stream()` / generated `Agent.stream()` async iterator emitting `WorkflowEvent` values (run lifecycle, model deltas, tool calls, policy decisions) for UIs, debugging, and observability.
62
+ - **Compiler backend target** — generated workflow-specific Python packages consume this runtime; see `nemoir-backend-python` in the main NemoIR repo.
63
+
64
+ ## Install
65
+
66
+ ```bash
67
+ pip install nemoir-runtime
68
+ ```
69
+
70
+ ## Quick start
71
+
72
+ ```python
73
+ import asyncio
74
+ from pathlib import Path
75
+ from nemoir_runtime import WorkflowRuntime, WorkflowManifest, Tool, ToolContext, ToolRegistry
76
+
77
+ # Define tools
78
+ async def read_file(*, path: Path, ctx: ToolContext) -> str:
79
+ return Path(path).read_text()
80
+
81
+ tools = ToolRegistry([
82
+ Tool(name="read_file", capability="fs.read", description="Read a file",
83
+ input_schema={"path": Path}, handler=read_file),
84
+ ])
85
+
86
+ # Load a manifest (typically generated by the NemoIR compiler)
87
+ manifest = WorkflowManifest(...)
88
+
89
+ runtime = WorkflowRuntime(manifest=manifest, tools=tools, stage_executor=my_executor)
90
+ result = await runtime.run({"task": "analyze code"})
91
+ print(result.output)
92
+ ```
93
+
94
+ See the [NemoIR project](https://github.com/nemoir) for the full compiler workflow (DSL → IR → generated package).
95
+
96
+ ## Requirements
97
+
98
+ - Python ≥ 3.11
99
+ - LiteLLM ≥ 1.0.0 (for `LiteLLMModelAdapter`; custom `ModelAdapter` implementations can avoid this dependency)
@@ -0,0 +1,51 @@
1
+ # NemoIR Runtime
2
+
3
+ Python runtime core for [NemoIR](https://github.com/nemoir) — an LLVM-inspired compiler stack for agentic workflows.
4
+
5
+ Executes compiled agent workflows as structured state machines with tool orchestration, policy enforcement, model-backed stage execution (via [LiteLLM](https://github.com/BerriAI/litellm)), and live event streaming.
6
+
7
+ ## Features
8
+
9
+ - **Workflow runtime** — state-machine execution with stage ordering, read/write resolution, transition selection, and run limits.
10
+ - **Tool framework** — capability-based tool registration, catalog-driven parameter validation, and policy-gated invocation (`fs.read`, `fs.write`, `user.confirm`, `os.shell`, `user.elicit`).
11
+ - **Policy engine** — deny and before-policies with expression evaluation (e.g., path containment guards).
12
+ - **Model integration** — `ModelStageExecutor` with LiteLLM adapter, structured output enforcement, tool-call loop, `ModelRouter` for per-stage model routing, and optional streaming via `ModelStreamingAdapter`.
13
+ - **Live event streaming** — `WorkflowRuntime.stream()` / generated `Agent.stream()` async iterator emitting `WorkflowEvent` values (run lifecycle, model deltas, tool calls, policy decisions) for UIs, debugging, and observability.
14
+ - **Compiler backend target** — generated workflow-specific Python packages consume this runtime; see `nemoir-backend-python` in the main NemoIR repo.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ pip install nemoir-runtime
20
+ ```
21
+
22
+ ## Quick start
23
+
24
+ ```python
25
+ import asyncio
26
+ from pathlib import Path
27
+ from nemoir_runtime import WorkflowRuntime, WorkflowManifest, Tool, ToolContext, ToolRegistry
28
+
29
+ # Define tools
30
+ async def read_file(*, path: Path, ctx: ToolContext) -> str:
31
+ return Path(path).read_text()
32
+
33
+ tools = ToolRegistry([
34
+ Tool(name="read_file", capability="fs.read", description="Read a file",
35
+ input_schema={"path": Path}, handler=read_file),
36
+ ])
37
+
38
+ # Load a manifest (typically generated by the NemoIR compiler)
39
+ manifest = WorkflowManifest(...)
40
+
41
+ runtime = WorkflowRuntime(manifest=manifest, tools=tools, stage_executor=my_executor)
42
+ result = await runtime.run({"task": "analyze code"})
43
+ print(result.output)
44
+ ```
45
+
46
+ See the [NemoIR project](https://github.com/nemoir) for the full compiler workflow (DSL → IR → generated package).
47
+
48
+ ## Requirements
49
+
50
+ - Python ≥ 3.11
51
+ - LiteLLM ≥ 1.0.0 (for `LiteLLMModelAdapter`; custom `ModelAdapter` implementations can avoid this dependency)
@@ -0,0 +1,75 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "nemoir-runtime"
7
+ version = "0.1.0"
8
+ description = "Python runtime core for NemoIR — execute compiled agent workflows as structured state machines with tool orchestration, policy enforcement, model-backed stage execution, and live event streaming."
9
+ readme = "README.md"
10
+ license = { file = "LICENSE" }
11
+ authors = [
12
+ { name = "NemoIR Contributors" },
13
+ ]
14
+ keywords = ["nemo", "agent", "workflow", "llm", "compiler", "runtime", "ai"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Programming Language :: Python :: 3.14",
24
+ "Topic :: Software Development :: Libraries :: Python Modules",
25
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
26
+ ]
27
+ urls = { Repository = "https://github.com/nemoir" }
28
+ requires-python = ">=3.11"
29
+ dependencies = [
30
+ "litellm>=1.0.0",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ dev = [
35
+ "pyright>=1.1.0",
36
+ "ruff>=0.1.0",
37
+ "pytest>=7.0.0",
38
+ "pytest-asyncio>=0.21.0",
39
+ ]
40
+
41
+ [tool.pytest.ini_options]
42
+ asyncio_mode = "auto"
43
+
44
+ [tool.pyright]
45
+ typeCheckingMode = "strict"
46
+ venv = ".venv"
47
+ venvPath = "."
48
+
49
+ [tool.ruff]
50
+ line-length = 100
51
+
52
+ [tool.ruff.lint]
53
+ select = ["ALL"]
54
+ ignore = [
55
+ "D",
56
+ "COM812",
57
+ "TRY003",
58
+ "ANN401",
59
+ "PLR0913",
60
+ "BLE001",
61
+ ]
62
+
63
+ [tool.ruff.lint.per-file-ignores]
64
+ "tests/**" = [
65
+ "S101",
66
+ "ARG001",
67
+ "S108",
68
+ "PLR2004",
69
+ "PT011",
70
+ "B017",
71
+ "ANN001",
72
+ ]
73
+ "tests/test_events.py" = ["PERF401"]
74
+ "tests/test_models.py" = ["PERF401"]
75
+ "tests/test_generated_package.py" = ["PERF401"]
@@ -0,0 +1,136 @@
1
+ from nemoir_runtime.capabilities import (
2
+ CAPABILITY_CATALOG,
3
+ CapabilityParam,
4
+ CapabilityParamType,
5
+ CapabilitySpec,
6
+ get_capability,
7
+ required_param_names,
8
+ )
9
+ from nemoir_runtime.errors import (
10
+ DataUnavailableError,
11
+ MaxStepsExceededError,
12
+ MissingCapabilityError,
13
+ ModelOutputValidationError,
14
+ ModelProviderError,
15
+ NemoIRRuntimeError,
16
+ NoTransitionMatchedError,
17
+ PolicyDeniedError,
18
+ PolicyEvaluationError,
19
+ StageOutputValidationError,
20
+ ToolInvocationError,
21
+ ToolValidationError,
22
+ WorkflowTimeoutError,
23
+ WorkflowValidationError,
24
+ )
25
+ from nemoir_runtime.events import (
26
+ WorkflowEvent,
27
+ WorkflowEventChannel,
28
+ WorkflowEventEmitter,
29
+ WorkflowEventKind,
30
+ WorkflowEventSink,
31
+ )
32
+ from nemoir_runtime.models import (
33
+ LiteLLMModelAdapter,
34
+ ModelAdapter,
35
+ ModelRequest,
36
+ ModelResponse,
37
+ ModelRouter,
38
+ ModelSpec,
39
+ ModelStageExecutor,
40
+ ModelStreamChunk,
41
+ ModelStreamingAdapter,
42
+ ModelToolCall,
43
+ supports_streaming,
44
+ )
45
+ from nemoir_runtime.runtime import (
46
+ ExprSpec,
47
+ GuardSpec,
48
+ InputSpec,
49
+ PolicySpec,
50
+ ReadSpec,
51
+ RefSpec,
52
+ RequiredCapabilitySpec,
53
+ RunOptions,
54
+ StageContext,
55
+ StageExecutor,
56
+ StageSpec,
57
+ TransitionSpec,
58
+ TriggerSpec,
59
+ WorkflowManifest,
60
+ WorkflowResult,
61
+ WorkflowRuntime,
62
+ WorkflowState,
63
+ WriteSpec,
64
+ )
65
+ from nemoir_runtime.tools import (
66
+ Tool,
67
+ ToolContext,
68
+ ToolRegistry,
69
+ tool,
70
+ )
71
+
72
+ __all__ = [
73
+ "CAPABILITY_CATALOG",
74
+ "CapabilityParam",
75
+ "CapabilityParamType",
76
+ "CapabilitySpec",
77
+ "DataUnavailableError",
78
+ "ExprSpec",
79
+ "GuardSpec",
80
+ "InputSpec",
81
+ "LiteLLMModelAdapter",
82
+ "MaxStepsExceededError",
83
+ "MissingCapabilityError",
84
+ "ModelAdapter",
85
+ "ModelOutputValidationError",
86
+ "ModelProviderError",
87
+ "ModelRequest",
88
+ "ModelResponse",
89
+ "ModelRouter",
90
+ "ModelSpec",
91
+ "ModelStageExecutor",
92
+ "ModelStreamChunk",
93
+ "ModelStreamingAdapter",
94
+ "ModelToolCall",
95
+ "NemoIRRuntimeError",
96
+ "NoTransitionMatchedError",
97
+ "PolicyDeniedError",
98
+ "PolicyEvaluationError",
99
+ "PolicySpec",
100
+ "ReadSpec",
101
+ "RefSpec",
102
+ "RequiredCapabilitySpec",
103
+ "RunOptions",
104
+ "StageContext",
105
+ "StageExecutor",
106
+ "StageOutputValidationError",
107
+ "StageSpec",
108
+ "Tool",
109
+ "ToolContext",
110
+ "ToolInvocationError",
111
+ "ToolRegistry",
112
+ "ToolValidationError",
113
+ "TransitionSpec",
114
+ "TriggerSpec",
115
+ "WorkflowManifest",
116
+ "WorkflowResult",
117
+ "WorkflowRuntime",
118
+ "WorkflowState",
119
+ "WorkflowTimeoutError",
120
+ "WorkflowValidationError",
121
+ "WriteSpec",
122
+ "get_capability",
123
+ "required_param_names",
124
+ "supports_streaming",
125
+ "tool",
126
+ ]
127
+
128
+ # Extended with Phase 5 event primitives (re-exported for convenience).
129
+ # Advanced users may import directly from nemoir_runtime.events.
130
+ __all__ += [
131
+ "WorkflowEvent",
132
+ "WorkflowEventChannel",
133
+ "WorkflowEventEmitter",
134
+ "WorkflowEventKind",
135
+ "WorkflowEventSink",
136
+ ]
@@ -0,0 +1,70 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from enum import Enum
5
+ from types import MappingProxyType
6
+ from typing import TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ from collections.abc import Mapping
10
+
11
+
12
+ class CapabilityParamType(Enum):
13
+ STRING = "string"
14
+ PATH = "path"
15
+ BOOL = "bool"
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class CapabilityParam:
20
+ name: str
21
+ type: CapabilityParamType
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class CapabilitySpec:
26
+ name: str
27
+ required_params: tuple[CapabilityParam, ...]
28
+
29
+ def has_required_param(self, name: str) -> bool:
30
+ return any(param.name == name for param in self.required_params)
31
+
32
+
33
+ CAPABILITY_CATALOG: Mapping[str, CapabilitySpec] = MappingProxyType(
34
+ {
35
+ "fs.read": CapabilitySpec(
36
+ name="fs.read",
37
+ required_params=(CapabilityParam("path", CapabilityParamType.PATH),),
38
+ ),
39
+ "fs.write": CapabilitySpec(
40
+ name="fs.write",
41
+ required_params=(
42
+ CapabilityParam("path", CapabilityParamType.PATH),
43
+ CapabilityParam("content", CapabilityParamType.STRING),
44
+ ),
45
+ ),
46
+ "os.shell": CapabilitySpec(
47
+ name="os.shell",
48
+ required_params=(CapabilityParam("command", CapabilityParamType.STRING),),
49
+ ),
50
+ "user.elicit": CapabilitySpec(
51
+ name="user.elicit",
52
+ required_params=(CapabilityParam("question", CapabilityParamType.STRING),),
53
+ ),
54
+ "user.confirm": CapabilitySpec(
55
+ name="user.confirm",
56
+ required_params=(CapabilityParam("message", CapabilityParamType.STRING),),
57
+ ),
58
+ },
59
+ )
60
+
61
+
62
+ def get_capability(name: str) -> CapabilitySpec | None:
63
+ return CAPABILITY_CATALOG.get(name)
64
+
65
+
66
+ def required_param_names(name: str) -> frozenset[str]:
67
+ spec = get_capability(name)
68
+ if spec is None:
69
+ return frozenset()
70
+ return frozenset(param.name for param in spec.required_params)
@@ -0,0 +1,57 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ class NemoIRRuntimeError(Exception):
5
+ pass
6
+
7
+
8
+ class ToolValidationError(NemoIRRuntimeError):
9
+ pass
10
+
11
+
12
+ class MissingCapabilityError(NemoIRRuntimeError):
13
+ pass
14
+
15
+
16
+ class ToolInvocationError(NemoIRRuntimeError):
17
+ pass
18
+
19
+
20
+ class PolicyDeniedError(NemoIRRuntimeError):
21
+ pass
22
+
23
+
24
+ class PolicyEvaluationError(NemoIRRuntimeError):
25
+ pass
26
+
27
+
28
+ class StageOutputValidationError(NemoIRRuntimeError):
29
+ pass
30
+
31
+
32
+ class DataUnavailableError(NemoIRRuntimeError):
33
+ pass
34
+
35
+
36
+ class NoTransitionMatchedError(NemoIRRuntimeError):
37
+ pass
38
+
39
+
40
+ class MaxStepsExceededError(NemoIRRuntimeError):
41
+ pass
42
+
43
+
44
+ class WorkflowTimeoutError(NemoIRRuntimeError):
45
+ pass
46
+
47
+
48
+ class WorkflowValidationError(NemoIRRuntimeError):
49
+ pass
50
+
51
+
52
+ class ModelProviderError(NemoIRRuntimeError):
53
+ pass
54
+
55
+
56
+ class ModelOutputValidationError(NemoIRRuntimeError):
57
+ pass
@@ -0,0 +1,105 @@
1
+ """Workflow event primitives for live streaming, UI observation, and debugging.
2
+
3
+ Defines the event types, channels, emitter, and sink that the runtime uses
4
+ to expose stage lifecycle, model deltas, tool calls, policy decisions, and
5
+ run-level outcomes as an observable async stream.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from collections.abc import Awaitable, Callable, Mapping
11
+ from dataclasses import dataclass, field
12
+ from datetime import UTC, datetime
13
+ from typing import Any, Literal
14
+
15
+ WorkflowEventKind = Literal[
16
+ "run_started",
17
+ "stage_started",
18
+ "model_delta",
19
+ "model_completed",
20
+ "tool_call_started",
21
+ "tool_call_completed",
22
+ "tool_call_failed",
23
+ "policy_checked",
24
+ "policy_denied",
25
+ "transition_selected",
26
+ "stage_completed",
27
+ "run_completed",
28
+ "run_failed",
29
+ ]
30
+
31
+ WorkflowEventChannel = Literal[
32
+ "assistant",
33
+ "progress",
34
+ "reasoning_summary",
35
+ "debug",
36
+ ]
37
+
38
+
39
+ @dataclass(frozen=True)
40
+ class WorkflowEvent:
41
+ kind: WorkflowEventKind
42
+ run_id: str
43
+ sequence: int
44
+ timestamp: datetime
45
+ stage_id: str | None = None
46
+ channel: WorkflowEventChannel | None = None
47
+ text: str | None = None
48
+ capability: str | None = None
49
+ tool_name: str | None = None
50
+ args: Mapping[str, Any] | None = None
51
+ output: Mapping[str, Any] | None = None
52
+ result: Any = None
53
+ error: str | None = None
54
+ transition_to: str | None = None
55
+ metadata: Mapping[str, Any] = field(default_factory=dict) # type: ignore[reportUnknownVariableType]
56
+
57
+
58
+ WorkflowEventSink = Callable[[WorkflowEvent], Awaitable[None]]
59
+
60
+
61
+ class WorkflowEventEmitter:
62
+ """Per-run event emitter with monotonic sequencing.
63
+
64
+ The emitter is cheap when the sink is ``None``: ``emit()`` still
65
+ constructs and returns the event (for tests), but no I/O happens.
66
+ """
67
+
68
+ def __init__(self, *, run_id: str, sink: WorkflowEventSink | None = None) -> None:
69
+ self._run_id = run_id
70
+ self._sink = sink
71
+ self._seq = 0
72
+
73
+ @property
74
+ def run_id(self) -> str:
75
+ return self._run_id
76
+
77
+ @property
78
+ def sequence(self) -> int:
79
+ return self._seq
80
+
81
+ @property
82
+ def has_sink(self) -> bool:
83
+ """True when an actual consumer is attached.
84
+
85
+ Use this to decide whether to activate provider-side streaming:
86
+ only stream when someone is listening.
87
+ """
88
+ return self._sink is not None
89
+
90
+ async def emit(
91
+ self,
92
+ kind: WorkflowEventKind,
93
+ **kwargs: Any,
94
+ ) -> WorkflowEvent:
95
+ self._seq += 1
96
+ event = WorkflowEvent(
97
+ kind=kind,
98
+ run_id=self._run_id,
99
+ sequence=self._seq,
100
+ timestamp=datetime.now(tz=UTC),
101
+ **kwargs,
102
+ )
103
+ if self._sink is not None:
104
+ await self._sink(event)
105
+ return event