Metadata-Version: 2.4
Name: nemoir-runtime
Version: 0.7.0
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.
Project-URL: Repository, https://github.com/nemoir
Author: NemoIR Contributors
License: MIT License
        
        Copyright (c) 2025 NemoIR
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: agent,ai,compiler,llm,nemo,runtime,workflow
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.11
Requires-Dist: litellm>=1.0.0
Provides-Extra: dev
Requires-Dist: pyright>=1.1.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Description-Content-Type: text/markdown

# NemoIR Runtime

Python runtime core for [NemoIR](https://github.com/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](https://github.com/BerriAI/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 integration** — `ModelStageExecutor` with LiteLLM adapter, structured output enforcement, tool-call loop, `ModelRouter` for per-stage model routing, and optional streaming via `ModelStreamingAdapter`.
- **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.
- **Compiler backend target** — generated workflow-specific Python packages consume this runtime; see `nemoir-backend-python` in the main NemoIR repo.

## Install

```bash
pip install nemoir-runtime
```

## Quick start

```python
import asyncio
from pathlib import Path
from nemoir_runtime import WorkflowRuntime, WorkflowManifest, Tool, ToolContext, ToolRegistry

# Define tools
async def read_file(*, path: Path, ctx: ToolContext) -> str:
    return Path(path).read_text()

tools = ToolRegistry([
    Tool(name="read_file", capability="fs.read", description="Read a file",
         input_schema={"path": Path}, handler=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](https://github.com/nemoir) 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.

```python
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:

```python
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:

```python
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:

```python
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)
