Skip to main content

Runtime enforcement layer for AI agents in production — client SDK.

Project description

Novarch

The runtime enforcement layer for AI agents. Novarch sits in the path of an agent's actions and, before an irreversible write executes, checks it against rules your team wrote in plain English — blocking clear violations and pausing borderline ones for a human to decide.

Per-action guardrails only see one call at a time. Novarch judges the agent's whole trajectory — every lookup it ran, the reasoning it gave, and the write it's about to commit — so it catches things a single-call check can't: a payment to a vendor whose bank details changed three days ago, a refund that's the customer's fourth this week, an off-policy promise with no exception ticket. When the gate fires, you get an auditable record citing the exact rule and the signals behind the decision.

This package is the client SDK — a thin, two-dependency library (httpx + pydantic) that talks to a Novarch deployment. The judge, the rules engine, and the operator triage dashboard run on the server side, hosted by us or in your VPC. To get an endpoint and an SDK key, reach support@novarch.ai or visit https://novarch.ai.

How it works

Three moving parts, one mental model:

  1. Read tools run freely and are buffered as evidence — the lookups and checks your agent does to inform a decision.
  2. A write tool is the gated moment — the instant the agent crosses its trust boundary. Novarch bundles the buffered reads + the agent's stated reasoning into an Action and submits it to the judge.
  3. The judge applies your plain-English rules and returns one of three verdicts: pass (write runs), hold (pauses for a human operator), or kill (blocked — the write never runs). Any uncertainty fails closed.

Your agent code catches exactly one exception, NovarchKillError, for every way a write can be stopped.

Install

pip install novarch

Optional framework adapters ship as extras (see Framework adapters):

pip install "novarch[langgraph]"    # LangGraph — one native seam, no per-tool wiring
pip install "novarch[langchain]"    # LangChain @tool composition
pip install "novarch[adk]"          # Claude Agent SDK (async tools)

Quickstart

Point the SDK at your deployment, then wrap your tools and your agent's entry point. This example uses GenericContext — the minimal, vertical-agnostic evidence shape (an id, a summary, an amount, plus any extra fields you want the judge to see).

export NOVARCH_SERVER_URL=https://<your-deployment>
export NOVARCH_SDK_KEY=<your-sdk-key>
from novarch import augur, NovarchKillError
from novarch.verticals.generic import GenericContext

# Reads run normally — the SDK buffers them as evidence for the judge.
@augur.tool(kind="read")
def lookup_customer(customer_id: str) -> dict:
    return crm.get(customer_id)

# The write is the gated moment. `context_builder` maps THIS call's arguments
# into the evidence your rules will be judged against.
@augur.tool(
    kind="write",
    action_type="issue_refund",
    context_builder=lambda customer_id, amount: GenericContext(
        record_id=customer_id,
        summary=f"Refund ${amount:.2f} to {customer_id}",
        amount=amount,
    ),
)
def issue_refund(customer_id: str, amount: float) -> str:
    return payments.refund(customer_id, amount)

# One @augur.session wraps the agent's entry point. Everything inside it —
# reads, reasoning, the write — is one governed trajectory.
@augur.session(tenant_id="acme", team_id="support", agent_id="refund-bot")
def handle_ticket(customer_id: str, amount: float) -> str:
    lookup_customer(customer_id)                      # buffered as evidence
    augur.set_reasoning("Customer reported a double charge; verified in CRM.")
    return issue_refund(customer_id, amount)          # ← judged right here

try:
    handle_ticket("cust-4821", 240.00)
except NovarchKillError as e:
    # Blocked by a rule, denied by an operator, or failed closed.
    log.warning("refund stopped at the gate: %s", e)

That's the whole surface: @augur.tool(kind="read"), @augur.tool(kind="write", ...), @augur.session(...), and augur.set_reasoning(...). Sync and async def tools both work — the decorators dispatch on the wrapped function. In the decorator form, context_builder receives the write tool's own arguments (here, customer_id and amount).

The rules see your evidence

Your rules live on the server and are written in plain English — that's the product. Whatever you put in the context is what they get to reason over. Suppose a rule reads:

"Block any refund over $500 for a customer with an open dispute."

GenericContext accepts arbitrary extra fields, so add the dispute count and the judge can key off it — no schema change:

context_builder=lambda customer_id, amount: GenericContext(
    record_id=customer_id,
    summary=f"Refund ${amount:.2f} to {customer_id}",
    amount=amount,
    open_dispute_count=disputes.count_open(customer_id),  # extra → visible to the rule
)

The judge sees this context plus every buffered read and your set_reasoning(...) text — the whole trajectory, not just the write's arguments.

Already using LangGraph?

Don't decorate every tool. Register one hook at LangGraph's native tool-execution seam and it gates the whole ToolNode:

from novarch import augur
from novarch.integrations.langgraph import NovarchToolGate, WriteSpec
from novarch.verticals.generic import GenericContext
from langgraph.prebuilt import ToolNode

@augur.session(tenant_id="acme", team_id="support", agent_id="refund-bot")
def run(question: str):
    gate = NovarchToolGate(
        # Classify each tool once. Reads buffer; writes gate.
        tool_kinds={"lookup_customer": "read", "issue_refund": "write"},
        write_specs={
            "issue_refund": WriteSpec(
                action_type="issue_refund",
                context_builder=lambda args: GenericContext(
                    record_id=args["customer_id"],
                    amount=args["amount"],
                ),
            ),
        },
    )
    node = ToolNode(tools, wrap_tool_call=gate.wrap())
    # ...build and invoke your graph with this node...

One difference to note: the WriteSpec context_builder receives a single args dict (the tool call's arguments), whereas the decorator form receives the tool's arguments directly.

At the seam, a pass executes, a kill raises NovarchKillError to halt the graph, an operator deny injects a blocked ToolMessage so the agent can recover, and any fail-closed case raises. Async graphs use awrap_tool_call=gate.awrap() and await gate.aclose() at teardown.

Keep the kill switch intact. LangGraph's default error handling re-raises, so a kill correctly halts the graph. But if you set a custom handle_tool_errors on the ToolNode, it will turn a BLOCKED verdict into an ordinary error message and the agent continues past it. If you set it at all, use kill_safe_handle_tool_errors() from the same module — it re-raises Novarch's halt signal and applies your fallback to everything else.

What happens at the gate

The table below is the decorator path. (Buyer-facing labels are what an operator sees in the dashboard.)

Verdict Buyer-facing Behavior
pass The write executes.
hold PAUSED The call blocks while a human operator approves or denies in the triage dashboard. Approve → the write runs; deny or timeout → NovarchKillError.
kill BLOCKED NovarchKillError immediately; the write never runs.

Under the LangGraph seam the only difference is holddeny: instead of raising, it injects a blocked ToolMessage so the agent can recover and try something else. kill and every fail-closed case still raise.

Fail closed. Any uncertainty — judge error, judge timeout, operator timeout, a server restart past the grace window — raises NovarchKillError with a [fail_closed_*] prefix in the message. Your agent never proceeds on a write Novarch couldn't confidently clear.

Two practical notes: every gated write is a blocking round-trip to the judge (one LLM call), and a hold blocks the caller until an operator decides or the poll timeout elapses (default 5 minutes — tune with @augur.session(poll_timeout_s=...)). NovarchKillError carries verdict, rule_id_cited, rationale, action_id, and session_id for your alerting and audit paths.

Framework adapters

All optional; the core SDK is framework-agnostic.

Extra Import Use
novarch[langgraph] from novarch.integrations.langgraph import NovarchToolGate, WriteSpec One native seam gates a whole ToolNode (shown above).
novarch[langchain] from novarch.integrations.langchain import augur_tool Wrap an @augur.tool function as a LangChain BaseTool.
novarch[adk] from novarch.integrations.claude_agent_sdk import augur_async_tool One decorator that stacks the Claude Agent SDK @tool over @augur.tool for async tools.

Typed vertical contexts

GenericContext covers any workflow. For domains with a rich, fixed signal set, typed contexts give stricter validation and turn known fields into first-class evidence:

  • from novarch.verticals.ap import APContext — accounts payable / vendor payments
  • from novarch.verticals.cs import CSContext — customer support / refunds

Reach for these when your rules key off vertical-specific signals (bank-change recency, dispute counts, exception-ticket references). Otherwise GenericContext plus a few extra keyword fields is enough — the judge sees any extras you pass.

Docs & license

Full documentation: https://docs.novarch.ai · Product: https://novarch.ai

This client SDK is licensed Apache-2.0. The Novarch platform — judge, rules engine, operator triage — is separately licensed and available as a managed service or a private deployment.

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

novarch-0.1.3.tar.gz (44.8 kB view details)

Uploaded Source

Built Distribution

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

novarch-0.1.3-py3-none-any.whl (53.1 kB view details)

Uploaded Python 3

File details

Details for the file novarch-0.1.3.tar.gz.

File metadata

  • Download URL: novarch-0.1.3.tar.gz
  • Upload date:
  • Size: 44.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for novarch-0.1.3.tar.gz
Algorithm Hash digest
SHA256 d2238fb3ed617c8ee9711948d8aa0468c83b086ea97709c2baf5fa42b10136c0
MD5 bf2ec1fcd0faedeb05db0639c94ab719
BLAKE2b-256 31dda4d2e2ba2f3b4629afa8fd6d85a8874e431b1b5b4480e26649d451ea1d3b

See more details on using hashes here.

Provenance

The following attestation bundles were made for novarch-0.1.3.tar.gz:

Publisher: release.yml on svemuri8/novarch

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file novarch-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: novarch-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 53.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for novarch-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 96b22143587ae647dcde12d63f4b7aa2b1c3b2e4b468f73bf7940a9814390ac8
MD5 9b58ef78763454f7c64ef78b42a66b60
BLAKE2b-256 487c177ea2df2c9f4bd5c8488d6d0a1f7248bceda5089dc8b4b034cd134cdc77

See more details on using hashes here.

Provenance

The following attestation bundles were made for novarch-0.1.3-py3-none-any.whl:

Publisher: release.yml on svemuri8/novarch

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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