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.
Project description
NemoIR Runtime
Python runtime core for NemoIR — an LLVM-inspired compiler stack for agentic workflows.
Executes compiled agent workflows as structured state machines with tool orchestration, policy enforcement, model-backed stage execution (via LiteLLM), and live event streaming.
Features
- Workflow runtime — state-machine execution with stage ordering, read/write resolution, transition selection, and run limits.
- Tool framework — capability-based tool registration, catalog-driven parameter validation, and policy-gated invocation (
fs.read,fs.write,user.confirm,os.shell,user.elicit). - Policy engine — deny and before-policies with expression evaluation:
and/orboolean combinators,eq/starts_with/containspredicates over bound trigger arguments, path containment and equality guards. - Model integration —
ModelStageExecutorwith LiteLLM adapter, structured output enforcement, tool-call loop,ModelRouterfor per-stage model routing, and optional streaming viaModelStreamingAdapter. - Deterministic stages —
exec:workflow stages run a fixed capability with bound args viaDeterministicStageExecutor, with no model call. Tool selection happens at runtime construction (fail-fast on no-match/ambiguous).Tool.output_schema/@tool(returns=…)declare tool return shapes so the runtime can match tools to stage outputs. - Live event streaming —
WorkflowRuntime.stream()/ generatedAgent.stream()async iterator emittingWorkflowEventvalues (run lifecycle, model deltas, tool calls, policy decisions) for UIs, debugging, and observability. - Compiler backend target — generated workflow-specific Python packages consume this runtime; see
nemoir-backend-pythonin the main NemoIR repo.
Install
pip install nemoir-runtime
Quick start
import asyncio
from pathlib import Path
from nemoir_runtime import WorkflowRuntime, WorkflowManifest, Tool, ToolContext, ToolRegistry
# Define tools — @tool derives input_schema from type hints; output_schema
# is derived from the return annotation (or set explicitly via returns=)
from nemoir_runtime import tool
@tool(capability="fs.read", description="Read a file")
async def read_file(*, path: Path, ctx: ToolContext) -> str:
return Path(path).read_text()
tools = ToolRegistry([read_file])
# Load a manifest (typically generated by the NemoIR compiler)
manifest = WorkflowManifest(...)
runtime = WorkflowRuntime(manifest=manifest, tools=tools, stage_executor=my_executor)
result = await runtime.run({"task": "analyze code"})
print(result.output)
See the NemoIR project for the full compiler workflow (DSL → IR → generated package).
Policy engine
Deny policies use expression evaluation to gate capability calls. Supported
predicates: eq (exact match), starts_with (prefix), contains
(substring or path containment). Boolean and/or combinators
short-circuit at runtime. in [...] is DSL sugar that lowers to or
of eq calls.
from nemoir_runtime import PolicySpec, ExprSpec, TriggerSpec, RefSpec
# deny os.shell(command) if not (
# command.eq("python run.py")
# or command.starts_with("git commit -m ")
# )
shell_allowlist = PolicySpec(
id="shell-allowlist",
kind="deny",
trigger=TriggerSpec(capability="os.shell", bind={"command": "command"}),
condition=ExprSpec(
kind="not",
expr=ExprSpec(
kind="or",
exprs=(
ExprSpec(
kind="method_call",
receiver=ExprSpec(kind="ref", ref=RefSpec(kind="bound", name="command")),
method="eq",
args=(ExprSpec(kind="literal", type="string", value="python run.py"),),
),
ExprSpec(
kind="method_call",
receiver=ExprSpec(kind="ref", ref=RefSpec(kind="bound", name="command")),
method="starts_with",
args=(ExprSpec(kind="literal", type="string", value="git commit -m "),),
),
),
),
),
)
# deny fs.write(path) if not path.eq(candidate_path)
write_allowlist = PolicySpec(
id="write-allowlist",
kind="deny",
trigger=TriggerSpec(capability="fs.write", bind={"path": "path"}),
condition=ExprSpec(
kind="not",
expr=ExprSpec(
kind="method_call",
receiver=ExprSpec(kind="ref", ref=RefSpec(kind="bound", name="path")),
method="eq",
args=(ExprSpec(kind="ref", ref=RefSpec(kind="input", name="candidate_path")),),
),
),
)
Official tools
nemoir-runtime ships with official, importable Tool implementations for every
capability in the catalog. Import exactly the tools you need:
from nemoir_runtime import ToolRegistry
from nemoir_runtime.official_tools import (
ask_user,
confirm_user,
edit_file,
read_file,
run_shell,
write_file,
)
tools = ToolRegistry([read_file, write_file, edit_file, run_shell, ask_user, confirm_user])
Pick a subset if you don't need every capability:
tools = ToolRegistry([read_file, edit_file, run_shell])
Policy boundary
Official tools validate inputs and perform the operation. They do not enforce workflow policy — path containment, write confirmation, shell allowlists, and similar authorization remain owned by NemoIR policies.
The user.elicit and user.confirm tools use the console and will raise on
non-interactive environments. Provide your own tool implementations for such
deployments.
Reasoning channel
WorkflowEventChannel includes a dedicated "reasoning" value for raw
provider chain-of-thought (DeepSeek delta.reasoning_content, Qwen, etc.).
It is distinct from "reasoning_summary", which is reserved for future
curated public summaries (Anthropic thinking, OpenAI o-series).
Reasoning forwarding is opt-in (default off) to preserve the default
posture of not exposing hidden/private chain-of-thought. Enable it via
ModelSpec.reasoning or a model config mapping:
agent = Agent(
model={"name": "openai/deepseek-v4-flash", "reasoning": "raw", ...},
tools=tools,
)
async for event in agent.stream(inputs):
if event.kind == "model_delta" and event.channel == "reasoning":
print(f"[reasoning] {event.text}", end="", flush=True)
elif event.kind == "model_delta" and event.channel == "assistant":
print(event.text, end="", flush=True)
Or per-run via RunOptions(reasoning="raw").
Reasoning text is never merged into the final structured-output content; stage output validation is unaffected.
Requirements
- Python ≥ 3.11
- LiteLLM ≥ 1.0.0 (for
LiteLLMModelAdapter; customModelAdapterimplementations can avoid this dependency)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nemoir_runtime-0.8.0.tar.gz.
File metadata
- Download URL: nemoir_runtime-0.8.0.tar.gz
- Upload date:
- Size: 74.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
46c0fe5320cd123cf12e24e7cd2b191917466d213bdd5304f045ab8adee8d4cf
|
|
| MD5 |
8b1fe37d1da84f0c48e3cce12cbeee3b
|
|
| BLAKE2b-256 |
d0ce5bb0b1138e6eaaefe686748dda3d2ca2130b6bd243c27cad37a29f0ff0f7
|
File details
Details for the file nemoir_runtime-0.8.0-py3-none-any.whl.
File metadata
- Download URL: nemoir_runtime-0.8.0-py3-none-any.whl
- Upload date:
- Size: 34.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
76da114364d0effba9fbb59d4270404cb8ef31e5cc9a1c3ac3a82bd7a4d03bae
|
|
| MD5 |
02e2fffc84815294758794acbb6b732d
|
|
| BLAKE2b-256 |
dd8d880c0c15b7925981de1145515596fcd39f38f6fbe80d5e5cd99342a51b4a
|