Skip to main content

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/or boolean combinators, eq/starts_with/contains predicates over bound trigger arguments, path containment and equality guards.
  • Model integrationModelStageExecutor with LiteLLM adapter, structured output enforcement, tool-call loop, ModelRouter for per-stage model routing, and optional streaming via ModelStreamingAdapter.
  • Deterministic stagesexec: workflow stages run a fixed capability with bound args via DeterministicStageExecutor, 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 streamingWorkflowRuntime.stream() / generated Agent.stream() async iterator emitting WorkflowEvent values (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-python in 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; custom ModelAdapter implementations can avoid this dependency)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nemoir_runtime-0.8.0.tar.gz (74.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nemoir_runtime-0.8.0-py3-none-any.whl (34.5 kB view details)

Uploaded Python 3

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

Hashes for nemoir_runtime-0.8.0.tar.gz
Algorithm Hash digest
SHA256 46c0fe5320cd123cf12e24e7cd2b191917466d213bdd5304f045ab8adee8d4cf
MD5 8b1fe37d1da84f0c48e3cce12cbeee3b
BLAKE2b-256 d0ce5bb0b1138e6eaaefe686748dda3d2ca2130b6bd243c27cad37a29f0ff0f7

See more details on using hashes here.

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

Hashes for nemoir_runtime-0.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 76da114364d0effba9fbb59d4270404cb8ef31e5cc9a1c3ac3a82bd7a4d03bae
MD5 02e2fffc84815294758794acbb6b732d
BLAKE2b-256 dd8d880c0c15b7925981de1145515596fcd39f38f6fbe80d5e5cd99342a51b4a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page