Skip to main content

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

badgerflow-0.1.0.tar.gz (257.4 kB view details)

Uploaded Source

Built Distribution

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

badgerflow-0.1.0-py3-none-any.whl (218.0 kB view details)

Uploaded Python 3

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

Hashes for badgerflow-0.1.0.tar.gz
Algorithm Hash digest
SHA256 1ff44f826463c29015db3c4e7eec50639f6502075906ca4eff91f9f97dcdbdca
MD5 d0cb9025dbfb2c87522a99564a47ee63
BLAKE2b-256 54b9fe851b34ce922d921f5df8af4389cc1846bd7f7e9e336d7c53b5f4520927

See more details on using hashes here.

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

Hashes for badgerflow-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e6a3bdc627c8c3d75e889d30b7726d9133db06989aca991c678e88246ff0874f
MD5 823e95fc788486bacfac849d78519360
BLAKE2b-256 4535bf0535b7340046be123a032554ab5b0db77fdf104b69ae832fb1f9ee2998

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page