badgerflow
The Python SDK for building pro-code agents that run as your own service and are governed by BadgerFlow anyway.
You deploy the container. BadgerFlow holds the release, the approval, the guardrail profile, the spend and the audit trail. Your agent never handles a credential and is never called by anyone but the platform.
Install
pip install 'badgerflow[server,langgraph]'
A LangGraph agent in one command
bf init claims-triage --langgraph
cd claims-triage
bf dev # serve it locally, no platform needed
bf init writes agent.py, badgerflow.yaml, a Dockerfile and a
.dockerignore. The agent is an ordinary StateGraph; the only BadgerFlow
addition is a @node(uses=...) decorator declaring what each node may touch:
@node(uses=Uses(knowledge=["claims-manual"]), determinism="recorded_effect")
async def retrieve(state: State) -> State:
chunks = await ctx.knowledge.retrieve(state["input"]["description"], top_k=4)
return {"excerpts": [c.text for c in chunks]}
app = Agent.from_langgraph(build_graph(), name="claims-triage", version="0.1.0",
input=Claim, output=Triage)
Those declarations are the contract. The platform compiles them into a graph it can render, checks them against what exists in your namespace at release time, and denies a node at runtime that reaches for something it never declared.
Work that outlives a request
An invocation is an HTTP request, and some agents run longer than one should be
held open. Call ctx.accept() and the platform stops waiting on the reply:
@app.run
async def triage(ctx: Context, claim: Claim) -> Triage:
ctx.accept() # answered 202; the platform now waits on events
await slow_work(claim) # minutes, not seconds
return Triage(...) # becomes the run's `complete` event
Nothing else changes. ctx.step, the governed clients and cancellation behave
identically, and the return value still becomes the run's result — it reaches
the platform as the terminal event rather than as the response body.
Two things to know. The platform's clock keeps running, so silence past the
timeout_seconds you registered fails the run; raise it at registration if the
work takes longer. And once you accept, you have accepted: the reply is 202 even
if the handler happens to finish first, so the same code cannot take one
governance path on a fast machine and another on a slow one.
Human review in a LangGraph agent
Use LangGraph's own interrupt() — nothing BadgerFlow-specific:
def gate(state):
decision = interrupt({"question": f"Approve {state['amount']}?"})
return {"decision": decision}
The run pauses, appears in the platform's Paused Inbox, and resumes when a
reviewer answers; interrupt() returns their answer. interrupt_before=[...]
works the same way, with the prompt naming the node it stopped before.
LangGraph needs a checkpointer to resume, and your pods are stateless: the pod
that resumes is usually not the pod that paused, often days later. So the SDK
binds BadgerFlowCheckpointSaver, which exports the graph's checkpoint into
the pause and restores it on the resume. You never configure it, and your
compiled graph is never modified.
Keep large values out of the graph state. The checkpoint travels with the pause and is capped; a state that is too big fails the run with a clear reason rather than producing a pause the platform cannot file. Hold a reference instead.
Streaming a run
Ask for text/event-stream and the invocation answers with the run's event
frames as they happen, the last one terminal:
data: {"seq":1,"type":"step_started","payload":{"node_id":"retrieve",...}}
data: {"seq":2,"type":"step_completed",...}
data: {"seq":3,"type":"complete","payload":{"output":{...}}}
Nothing in the handler changes: the same ctx.step blocks and the same return
value. What changes is where the evidence goes. On a streamed invocation the
frames ride the response instead of being posted to the platform's event
ingest, because both would record the run twice.
Register and release
Deploy the container, then point the platform at it:
bf sync --wait-for-endpoint 120 --image "$IMAGE@$DIGEST"
The platform fetches /.well-known/badgerflow-agent.json from your endpoint and
records what it saw, so a registration is evidence rather than a claim.
bf sync exits non-zero when what the platform fetched is not what your
checkout compiles to, which is what makes it safe in CI.
Registering does not change what runs. A governed release does:
bf release submit --wait # then a second human approves it in the UI
Each environment runs its own BadgerFlow with its own approvers, so run this once per environment. Pass the image digest, never a tag: it is what a rollback names and what makes "the same build" promoted from UAT to production provable.
Commands
bf init [--langgraph] |
scaffold a project |
bf dev |
serve locally, run-token verification off |
bf validate |
build the manifest offline, print its hash |
bf compile [--check] |
the graph the platform will see, and its hash |
bf register / bf sync |
register a build; sync is the CI form |
bf status |
registration, release, and hash agreement |
bf release submit | status |
the governed release and its scorecard |
Versioning
The SDK follows semantic versioning. Its version is not decoration: it lands in
every manifest's sdk block and the platform stores it with the release, so it
is part of what an auditor sees. The wire contract is versioned separately
as contract_version — an SDK major bump does not imply a contract bump, and a
contract bump is announced on its own.
Requires
Python 3.11+. The server extra pulls FastAPI and uvicorn, langgraph pulls
LangGraph and langchain-core, otel pulls the OpenTelemetry SDK. Without an
extra the corresponding surface is inert rather than broken.
Licence
Apache-2.0, and it covers this SDK only. The BadgerFlow platform the SDK
talks to is not open source and is licensed separately. See LICENSE and
NOTICE.
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 badgerflow-0.1.0.tar.gz.
File metadata
- Download URL: badgerflow-0.1.0.tar.gz
- Upload date:
- Size: 257.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1ff44f826463c29015db3c4e7eec50639f6502075906ca4eff91f9f97dcdbdca
|
|
| MD5 |
d0cb9025dbfb2c87522a99564a47ee63
|
|
| BLAKE2b-256 |
54b9fe851b34ce922d921f5df8af4389cc1846bd7f7e9e336d7c53b5f4520927
|
File details
Details for the file badgerflow-0.1.0-py3-none-any.whl.
File metadata
- Download URL: badgerflow-0.1.0-py3-none-any.whl
- Upload date:
- Size: 218.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e6a3bdc627c8c3d75e889d30b7726d9133db06989aca991c678e88246ff0874f
|
|
| MD5 |
823e95fc788486bacfac849d78519360
|
|
| BLAKE2b-256 |
4535bf0535b7340046be123a032554ab5b0db77fdf104b69ae832fb1f9ee2998
|