Metadata-Version: 2.4
Name: badgerflow
Version: 0.1.0
Summary: BadgerFlow Python SDK: governed pro-code agents, the platform REST client, and the bf / agiel CLIs
License-Expression: Apache-2.0
Keywords: langgraph,agents,llm,governance,badgerflow,mlops
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.0
Requires-Dist: pyjwt[crypto]>=2.8
Requires-Dist: openai>=1
Requires-Dist: typer>=0.12
Requires-Dist: pyyaml>=6
Requires-Dist: rich>=13
Requires-Dist: jsonschema>=4.23
Provides-Extra: langgraph
Requires-Dist: langgraph<2,>=1.2; extra == "langgraph"
Requires-Dist: langchain-core<2,>=1.6; extra == "langgraph"
Requires-Dist: langchain-openai<2,>=1.6; extra == "langgraph"
Provides-Extra: otel
Requires-Dist: opentelemetry-sdk>=1.27; extra == "otel"
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.27; extra == "otel"
Requires-Dist: opentelemetry-instrumentation-httpx>=0.48b0; extra == "otel"
Requires-Dist: opentelemetry-instrumentation-logging>=0.48b0; extra == "otel"
Provides-Extra: server
Requires-Dist: fastapi>=0.115; extra == "server"
Requires-Dist: uvicorn>=0.30; extra == "server"
Requires-Dist: prometheus_client>=0.20; extra == "server"
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
Requires-Dist: respx>=0.21; extra == "dev"
Requires-Dist: ruff>=0.6; extra == "dev"
Provides-Extra: langchain
Requires-Dist: langchain>=0.2; extra == "langchain"
Requires-Dist: langchain-core>=0.2; extra == "langchain"
Provides-Extra: crewai
Requires-Dist: crewai>=0.30; extra == "crewai"
Provides-Extra: autogen
Requires-Dist: pyautogen>=0.2; extra == "autogen"
Dynamic: license-file

# badgerflow

The Python SDK for building **pro-code agents** that run as your own service and
are governed by [BadgerFlow](https://github.com/facileai/agiel) 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

```bash
pip install 'badgerflow[server,langgraph]'
```

## A LangGraph agent in one command

```bash
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:

```python
@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:

```python
@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:

```python
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:

```bash
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:

```bash
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`.
