Metadata-Version: 2.5
Name: nemoir-runtime
Version: 0.11.1
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/hkalexling/nemoir-python-runtime
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
Requires-Dist: openai>=1.40.0
Provides-Extra: dev
Requires-Dist: pyright==1.1.410; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Requires-Dist: ruff==0.15.18; extra == 'dev'
Provides-Extra: trace
Requires-Dist: cryptography>=44.0.0; extra == 'trace'
Description-Content-Type: text/markdown

# NemoIR Runtime

Python runtime core for [NemoIR](https://github.com/hkalexling/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`.
- **Deterministic stages** — `exec:` 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 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 the [Python target guide](https://github.com/hkalexling/nemoir/blob/master/docs/targets/python.md) in the public compiler repo.

## Install

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

## Compiler references

NemoIR is a research-pilot compiler stack. Canonical workflow language, IR semantics, and compiler-target behavior live in the public compiler repo:

- [Compiler repo](https://github.com/hkalexling/nemoir)
- [DSL and IR spec](https://github.com/hkalexling/nemoir/blob/master/docs/dsl-and-ir.md)
- [Python target guide](https://github.com/hkalexling/nemoir/blob/master/docs/targets/python.md)

## Quick start

```python
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 compiler references above for the full DSL → IR → generated-package workflow.

## 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.

## Trace verification

The runtime writes **NemoTrace** archives (`*.nemotrace`): one redacted,
portable execution record per run, optionally with an encrypted replay vault.
Install the vault crypto extra for unlock/replay: `pip install
"nemoir-runtime[trace]"`.

The artifact format, capture profiles, verification and replay levels, and
publication gates are documented in the public compiler docs:
[Trace artifacts](https://github.com/hkalexling/nemoir/blob/master/docs/trace.md).

The bundled `nemotrace` CLI verifies an archive and reports its levels —
integrity, structural, semantic, and replayability — reusing the same library
reports as the viewer:

```bash
nemotrace verify run.nemotrace                          # public levels only
nemotrace verify run.nemotrace --unlock env:VAULT_PW    # + semantic evidence
nemotrace verify run.nemotrace --replay file:./pw.txt   # + taped replay
```

`--unlock` and `--replay` are mutually exclusive and take a passphrase source:
`env:VAR`, `file:PATH`, or `prompt`. Output is a stable `key: value` report on
stdout; the exit code is `0` when the requested level passed, `1` when it
failed, and `2` for usage errors. Passphrase values, vault plaintext, and
stack traces are never printed. `python -m nemoir_runtime verify …` is
equivalent when the console script is not on `PATH`.

Taped replay re-executes the recorded state machine with recorded model/tool
fixtures only — no provider calls, no real tool effects. It is deterministic
playback of captured evidence, not a live rerun.

### Publishing a trace (`publication-v1`)

An `audit` archive is safe-by-default local capture, not automatically safe to
post. Publication is a separate, reviewed transform that produces one stricter,
vault-free `publication` artifact:

```bash
# 1. project for review (writes a disclosure report; publishes nothing)
nemotrace scan-publication runs/<id>/run.nemotrace

# 2. bind your review to the projection digest it reported
nemotrace attest-publication \
  --report runs/<id>/run.nemotrace.publication-report.json \
  --reviewer "Your Name" --license CC-BY-4.0 \
  --consent "I reviewed the disclosure report and certify this trace is safe to publish."

# 3. write the attested archive (+ its report sidecar)
nemotrace prepare-publication runs/<id>/run.nemotrace published/run.nemotrace \
  --attest runs/<id>/run.nemotrace.publication-report.json.attestation.json
```

Publication refuses a vault-bearing or already-published source, an
interrupted run, incomplete compiler provenance, a projection the attestation
does not cover, and any `secrets-v1` scanner finding. By default it drops
static tool names and replaces alias-relative paths with opaque `path-N`
refs; `--allow-tool-name NAME` (repeatable) and `--keep-relative-paths` opt
individual review decisions back in, and each choice changes the projection
digest you are asked to attest. Reports and attestations are local review
artifacts — they are never written inside the archive.

Redaction reduces risk; it cannot prove that reviewed identifiers or approved
scalar metrics are non-sensitive. Human review remains mandatory.

### Sharing a trace on a public Gist

Publishing stays a deliberate, credential-owning step. The CLI gates the
artifact and verifies the result; the upload itself uses your own Git/GitHub
credential (GitHub's API models file content as JSON text, so a binary
`.nemotrace` must go over the Gist's Git remote — the viewer never uploads
anything):

```bash
nemotrace publish-plan published/run.nemotrace \
  --title "CVXPYgen autoresearch run" --license CC-BY-4.0   # gate + runbook

# ...run the printed gh/git commands with your credential...

nemotrace publish-verify <gist-id> --filename run.nemotrace \
  --expect-content-identity sha256:...
```

`publish-plan` refuses an audit/replay archive, a vault, an unattested
archive, a failed scanner, incomplete provenance, and anything over the 8 MiB
public budget, then prints the upload runbook, a suggested Gist README, the
Gist permanence warning, and a ready catalog entry.
`publish-verify` re-downloads the public Gist through the documented
metadata → pinned revision → `raw_url` path, re-verifies the archive, and
prints the pinned citation URL. Remember that a public Gist is public and
durable, and a secret Gist is not private storage.

## Requirements

- Python ≥ 3.11
- LiteLLM ≥ 1.0.0 (for `LiteLLMModelAdapter`; custom `ModelAdapter` implementations can avoid this dependency)

## Releasing

Maintainers should follow [RELEASE.md](RELEASE.md); PyPI publication is
performed only by the trusted GitHub Actions workflow.
