Skip to main content

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.

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.2.0.tar.gz (47.3 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.2.0-py3-none-any.whl (56.4 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: novarch-0.2.0.tar.gz
  • Upload date:
  • Size: 47.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for novarch-0.2.0.tar.gz
Algorithm Hash digest
SHA256 2123788fc75517cd9737acafdd75760c3013d38ce03eee9b4e2dc972c3cdc963
MD5 61e108cd38f0259321712c7811470a87
BLAKE2b-256 5c7c083486caa3e1be12462999dbb0213d57eb318cb1ae1f1ebe40554d67d4e0

See more details on using hashes here.

Provenance

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

Publisher: publish.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.2.0-py3-none-any.whl.

File metadata

  • Download URL: novarch-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 56.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for novarch-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 64753b671b783a4d4ee00ecda5dd5f4ec8228ac3de221dca56e1c0a05f2a8b90
MD5 ca34105295243d02b42570436ca5432b
BLAKE2b-256 e47df334ce88fd3e52c86c81a49cf882f85bb88b5d744181c391f3a63d5af0c2

See more details on using hashes here.

Provenance

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

Publisher: publish.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