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:
- Read tools run freely and are buffered as evidence — the lookups and checks your agent does to inform a decision.
- 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
Actionand submits it to the judge. - The judge applies your plain-English rules and returns one of three
verdicts:
pass(write runs),hold(pauses for a human operator), orkill(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
killcorrectly halts the graph. But if you set a customhandle_tool_errorson theToolNode, it will turn a BLOCKED verdict into an ordinary error message and the agent continues past it. If you set it at all, usekill_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 hold → deny: 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 paymentsfrom 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d2238fb3ed617c8ee9711948d8aa0468c83b086ea97709c2baf5fa42b10136c0
|
|
| MD5 |
bf2ec1fcd0faedeb05db0639c94ab719
|
|
| BLAKE2b-256 |
31dda4d2e2ba2f3b4629afa8fd6d85a8874e431b1b5b4480e26649d451ea1d3b
|
Provenance
The following attestation bundles were made for novarch-0.1.3.tar.gz:
Publisher:
release.yml on svemuri8/novarch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
novarch-0.1.3.tar.gz -
Subject digest:
d2238fb3ed617c8ee9711948d8aa0468c83b086ea97709c2baf5fa42b10136c0 - Sigstore transparency entry: 2191830768
- Sigstore integration time:
-
Permalink:
svemuri8/novarch@6f49b3bde7264def7f7278e1443a6e7d2e428ab2 -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/svemuri8
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6f49b3bde7264def7f7278e1443a6e7d2e428ab2 -
Trigger Event:
push
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
96b22143587ae647dcde12d63f4b7aa2b1c3b2e4b468f73bf7940a9814390ac8
|
|
| MD5 |
9b58ef78763454f7c64ef78b42a66b60
|
|
| BLAKE2b-256 |
487c177ea2df2c9f4bd5c8488d6d0a1f7248bceda5089dc8b4b034cd134cdc77
|
Provenance
The following attestation bundles were made for novarch-0.1.3-py3-none-any.whl:
Publisher:
release.yml on svemuri8/novarch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
novarch-0.1.3-py3-none-any.whl -
Subject digest:
96b22143587ae647dcde12d63f4b7aa2b1c3b2e4b468f73bf7940a9814390ac8 - Sigstore transparency entry: 2191830798
- Sigstore integration time:
-
Permalink:
svemuri8/novarch@6f49b3bde7264def7f7278e1443a6e7d2e428ab2 -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/svemuri8
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6f49b3bde7264def7f7278e1443a6e7d2e428ab2 -
Trigger Event:
push
-
Statement type: