nodloop for Python
Record what your agent answered, take a person's verdict on it, and hand the corrections they approve to the agent's next run in the same situation. This is the Python client of nodloop.
pip install nodloop
pip install "nodloop[langchain]" # or openai-agents, claude-agent
The client starts a local nodloop mcp over stdio, so no server runs. It uses NODLOOP_BIN, then the binary the Claude Code plugin keeps under ~/.nodloop/bin, then nodloop on PATH, and otherwise downloads the release of its own version from GitHub and checks it against the release checksums. It requires Python 3.10 or newer and is tested on Linux with Python 3.12.
The loop
from nodloop import Client
acme = {"tenant": ["acme"], "task": ["refund"]}
async with Client() as c:
knowledge = await c.knowledge("support-bot", acme)
prompt = f"{system_prompt}\n\n{knowledge.text}" # empty text when nothing is approved
answer = await my_agent(prompt)
run = await c.record("support-bot", answer, labels=acme, applied=knowledge.items)
# later, when a person reads the answer
await c.judge(run, "reject", reason="the refund window was missing", reason_code="scope")
candidate = await c.propose("judgment", "Quote the refund window before the steps", from_run=run)
approval = await c.approve(candidate.id, candidate.version, approver="ann")
From then on every knowledge("support-bot", acme) carries the item, and a run of another tenant never does. judge takes approve, edit with edited= holding the corrected output, reject with what was wrong, or withdraw to take back the verdict before it.
| Method | Returns |
|---|---|
knowledge(producer, labels) |
Knowledge with items and the text to put in the prompt |
record(producer, output, *, labels, applied, subject) |
the run id a verdict cites |
judge(run, verdict, reason, reason_code, edited, reviewer) |
nothing |
propose(kind, content, from_run, producer, labels, new_labels, item_id, veto) |
Candidate with id, version, status and overlaps, the current items of the same kind one run may carry with it |
approve(item_id, version, approver, labels=) |
Approves as proposed, or for the runs carrying labels when given, and {} for every run of the producer. Approval with id, version, status, approver, veto, compaction_due when one run would carry more than five items, and export_error and folder_error when a step after the approval failed |
check_call(tool, arguments, producer) |
Decision with action of allow, block or ask, veto and reason |
A tool that refuses, such as a proposal from a run nobody corrected, raises ToolError with the reason.
Adapters
LangChain create_agent middleware fetches the items once per run and puts them in the system message, records the final answer and refuses a vetoed tool call:
from nodloop.langchain import NodloopMiddleware
mw = NodloopMiddleware(c, "support-bot", acme)
agent = create_agent(model, tools, middleware=[mw])
await agent.ainvoke({"messages": [...]})
await c.judge(mw.run, "approve")
OpenAI Agents SDK hooks and a tool input guardrail:
from nodloop.openai_agents import NodloopHooks, veto_guardrail
hooks = NodloopHooks(c, "support-bot", acme)
agent = Agent(name="support", instructions=hooks.instructions("You help with refunds"), tools=[...])
await Runner.run(agent, "refund?", hooks=hooks)
Pass veto_guardrail(c, "support-bot") in a tool's tool_input_guardrails.
Claude Agent SDK hooks, the same loop the Claude Code plugin runs:
from nodloop.claude_agent import NodloopHooks
hooks = NodloopHooks(c, "support-bot", acme)
options = ClaudeAgentOptions(hooks=hooks.hooks())
Use one adapter instance per run or conversation, since it keeps the items it handed out and the id of the run it recorded.
A shared server
Client(url="https://nodloop.example/mcp", key="nl_...") connects to nodloop-server serve instead of a local binary. The key decides the tenant and what the client may do, and an approval through it is recorded under the key's name.
More
The nodloop README covers labels, vetoes, reports and the server. MIT licensed.
Metadata
Release files for nodloop 0.8.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| nodloop-0.8.0.tar.gz | 174.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| nodloop-0.8.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 186.0 kB
Release files / nodloop-0.8.0.tar.gz
| Download URL | nodloop-0.8.0.tar.gz |
|---|---|
| Size | 174.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7da84af7a2006d0271db9d8f4153b57b9165f4d4729a447753fd58331a135d23
|
|
BLAKE2b-256 checksum How to use checksums |
c42bb8cea609f7446f000e3ff954ed08cf5670758711e16c9ad2a62e4f8c2aa1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / nodloop-0.8.0-py3-none-any.whl
| Download URL | nodloop-0.8.0-py3-none-any.whl |
|---|---|
| Size | 11.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ace5be326a4940ada33950135fe5f85eeddc47883daab132ba4a9d32ff622a21
|
|
BLAKE2b-256 checksum How to use checksums |
45ed36afca3880b02e3c1a9030b6c559dd999535ed8ce27f7d75107dcabb8ee2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|