{"slug": "show-hn-boundflow-an-open-source-control-plane-for-ai-agents", "title": "Show HN: BoundFlow – an open-source control plane for AI agents", "summary": "BoundFlow, an open-source control plane for AI agents, enforces cost caps, approval gates, and self-healing policies on unattended LLM workflows. The backend is Apache-2.0 licensed and self-hostable, with a Python SDK available, and offers a managed cloud option in early access.", "body_md": "**The operational layer for the LLM agents and workflows you run unattended — cost caps, approval gates, and self-healing policy, enforced by a control plane.**\n\nImportant\n\n**Public preview (pre-1.0).** The engine is complete and covered by Go, mock-LLM,\nand live-LLM test suites, but it hasn't yet been run in production with external\nusers. APIs — including the gRPC protobufs — may change before 1.0. We're looking\nfor early adopters and design partners: [reach out](mailto:hello@boundflow.dev).\n\nBoundFlow runs long-running, stateful agent workflows and enforces the guardrails\nyou'll want before running agents unattended: per-run **cost caps**, automatic **model switching** on\ncost/loop policies, **human approval gates** before sensitive actions, tool-call\nlimits, retries, cooldowns, and versioned rollbacks. You write agents and\nworkflows against a clean async SDK; the control plane schedules, dispatches, and\ngoverns them.\n\nInference is **bring-your-own** — your agents call Claude with your own Anthropic\nkey, running in your worker. The backend never sees it and never pays for tokens.\nYour keys, your data, and your token spend stay on your side of the wire.\n\n**In practice:** a support-triage workflow that may spend up to **$0.25/run**, must\nget a **human's sign-off** before issuing a refund, **downgrades to Haiku** when\ncosts spike, and **auto-rolls-back** to the last good version if it starts\nfailing — none of that logic living in your agent code. You declare it as policy;\nthe control plane enforces it and keeps a durable, queryable **audit log** of every\napproval and policy decision.\n\nBoundFlow is *not* a prompt framework, an inference provider, or an agent-builder —\nit's the operational layer *around* the agents you build.\n\n**Backend**— open source (Apache-2.0), self-hostable as a container.** Python SDK**— open source (MIT),`pip install boundflow`\n\n.**Docs**— concepts, governance, deployment, and API reference in.`docs/`\n\n**BoundFlow Cloud**— prefer not to self-host? Managed hosting (early access) — see[below](#hosted-boundflow-cloud).\n\n**Agents that take real actions need a control plane that takes real action when\nthey go wrong.** Most tools *watch* your agents; BoundFlow *intervenes* — at both\nlevels. On the **agent**: cap its spend, swap its model mid-run. On the\n**workflow**: gate a risky step for human sign-off, cool it down, roll it back to a\nknown-good version, or pause it outright. It's **workflow-aware, not just\nagent-aware** — because it runs the whole workflow, not just the model call:\nscheduling each run, carrying state across steps, recovering from failures, and\ndriving it through its lifecycle, with the agent as just one operation inside a\ndurable, multi-step process it owns end to end.\n\nThe moment agents run unattended you need answers to: *What if it loops? What if\nit spends $50? What if it's about to do something irreversible? Which model should\nit use, and when should that change?* BoundFlow makes those **policies** instead of\ncode:\n\n| Concern | BoundFlow gives you |\n|---|---|\n| Runaway cost | A hard `max_cost_usd` cap that halts a run the moment its cost crosses budget |\n| Irreversible actions | Approval gates — the workflow parks for a human decision before it acts |\n| Loops & output blowups | Runtime limits: `max_llm_calls` , `max_tokens_per_call` , per-tool call caps |\n| Wrong model for the job | Agent lifecycle policy — react to signals over the agent's entire life (e.g. downgrade a costly model to a cheaper one past a certain budget) |\n| Degrading or failing workflows | Self-healing lifecycle policy — cool down, pause, or auto-roll-back to a known-good version |\n| Flying blind | OpenTelemetry-native run traces shipped to your stack (Jaeger, Tempo, Langfuse, …), plus a durable, queryable audit log of every approval and policy decision |\n| Your keys & token spend | Bring-your-own inference — agents call Claude with your key; the backend never sees it or pays for tokens (cache-aware, per-tenant cost) |\n\nPolicies are evaluated server-side (lifecycle) and enforced SDK-side (runtime), with per-invocation metrics — cost, tokens, LLM calls, per-tool counts/failures — collected on every run.\n\nThe **BoundFlow backend** is the control plane — self-host it, or run it on\nBoundFlow Cloud. Either way, your **worker** connects to it over gRPC and runs the\nactual agents, with your Anthropic key, in your environment; the backend schedules,\ndispatches, governs, and audits, and never sees your key or your inference traffic.\n\n```\n   ┌─────────────────────┐      gRPC        ┌────────────────────────┐\n   │  Your client / SDK  │ ───────────────▶ │                        │\n   └─────────────────────┘  invoke·approve  │   BoundFlow backend    │\n                             ·query         │   (control plane)      │\n   ┌─────────────────────┐   gRPC stream    │                        │\n   │  Your worker        │ ◀──────────────▶ │  schedules·dispatches  │\n   │  runs agents+tools  │  launch/result   │  ·governs·audits       │\n   │  with your API key  │                  └────────────────────────┘\n   └─────────────────────┘\n```\n\nUnder the hood the backend runs as three process modes (`server`\n\n, `scheduler`\n\n,\n`worker`\n\n) off one binary sharing Postgres — see\n** docs/concepts.md** for the full breakdown and the lifecycle\nstates.\n\n``` python\nfrom boundflow import AgentDefinition, BoundFlowWorker, Complete, ControlPlaneClient, WorkflowConfig\nfrom boundflow.anthropic_client import AnthropicLlmClient\n\nworker = BoundFlowWorker(llm=AnthropicLlmClient(...))  # endpoints + key from env\n\n@worker.workflow(\"triage\", version=1)\nasync def triage(ctx):\n    ctx.add_context(\"ticket\", \"...\")\n    await ctx.run_agent(AgentDefinition(\n        name=\"analyst\", model=\"claude-haiku-4-5\",\n        system_prompt=\"Diagnose the issue.\", output_schema={\"summary\": {\"type\": \"string\"}},\n    ))\n    return Complete()\n```\n\n**Bring your own provider via LangChain.** Wrap any tool-calling LangChain chat\nmodel in `LangChainLlmClient`\n\nand the governance is identical — OpenAI, Google,\nBedrock, and the rest of LangChain's ecosystem run under the same cost caps, model\npolicies, and approval gates:\n\n``` python\nfrom langchain_anthropic import ChatAnthropic          # or ChatOpenAI, ChatVertexAI, ...\nfrom boundflow.langchain_client import LangChainLlmClient\n\nworker = BoundFlowWorker(llm=LangChainLlmClient(ChatAnthropic(model=\"claude-haiku-4-5\")))\n```\n\nInstall with `pip install \"boundflow[langchain]\"`\n\n; see\n[ boundflow.examples.langchain_adapter](/boundflow/boundflow/blob/main/sdk/python/boundflow/examples/langchain_adapter.py)\nfor a runnable end-to-end example.\n\n**Orchestrate with LangGraph, governed by BoundFlow.** Build a LangGraph agent\ngraph *inside* a workflow with its nodes calling `ctx.run_agent`\n\n, so LangGraph\nowns the routing while BoundFlow governs every agent step and the workflow as a\nwhole. See [Integrations](/boundflow/boundflow/blob/main/docs/integrations.md) and the runnable\n[ boundflow.examples.langgraph_workflow](/boundflow/boundflow/blob/main/sdk/python/boundflow/examples/langgraph_workflow.py).\n\nWorkflows are **multi-step and stateful**: an operation can park for a human\ndecision or chain into a follow-on operation, and the workflow resumes where it\nleft off — nothing irreversible runs until the branch it's gated behind does.\n\n``` python\nfrom boundflow import AwaitApproval, Next, Complete\n\n@worker.workflow(\"refund\", version=1)\nasync def refund(ctx):\n    await ctx.run_agent(analyst)                    # step 1: reason about the request\n    return AwaitApproval(                            # park — nothing irreversible yet\n        on_approve=Next(\"issue_refund\", ctx.context),\n        on_reject=Complete(),\n        justification=\"Approve the $5,000 refund?\",\n    )\n\n@worker.operation(\"refund\", \"issue_refund\")         # step 2: runs only after a human approves\nasync def issue_refund(ctx):\n    ...                                              # the sensitive action, now sanctioned\n    return Complete()\n```\n\nGovernance is applied from the control plane — three layers, from a per-run cap to self-healing version rollback:\n\n``` python\nfrom boundflow import (\n    RuntimePolicy, AgentRule, AgentMetric, Op, SetModel,\n    WorkflowRule, WorkflowMetric, SetVersion,\n)\n\n# 1. Runtime — a hard cap enforced *during* every run:\nawait cp.set_agent_runtime_policy(wf.id, \"analyst\", RuntimePolicy(max_cost_usd=0.25))\n\n# 2. Agent lifecycle — after runs, downgrade the model if cost trends high:\nawait cp.set_agent_lifecycle_policy(wf.id, \"analyst\", [\n    AgentRule(metric=AgentMetric.COST_USD, op=Op.GT, threshold=0.20, window=5,\n              action=SetModel(value=\"claude-haiku-4-5\")),\n])\n\n# 3. Workflow lifecycle — after repeated failures, roll the whole workflow back\n#    to a known-good version automatically:\nawait cp.set_workflow_lifecycle_policy(wf.id, [\n    WorkflowRule(metric=WorkflowMetric.NUM_FAILURES, threshold=3,\n                 action=SetVersion(target=1)),\n])\n```\n\nWorkflow rules can also `Pause`\n\na workflow or put it on `Cooldown`\n\ninstead of\nrolling back. See [ sdk/python/boundflow/examples/](/boundflow/boundflow/blob/main/sdk/python/boundflow/examples) for runnable examples.\n\nGet a governed agent running in a few minutes. Full walkthrough: ** QUICKSTART.md**.\n\n```\n# 1. Set a database password (any strong secret)\necho \"BOUNDFLOW_DB_PASSWORD=$(openssl rand -hex 16)\" > .env\n\n# 2. Start the backend (Postgres + server + scheduler + worker)\ndocker compose -f docker-compose.dist.yml up -d\n\n# 3. Provision an API key\ndocker compose -f docker-compose.dist.yml run --rm server -mode=provision -name=me\nexport BOUNDFLOW_API_KEY=<printed key>\n\n# 4. Install the SDK and bring your Anthropic key\npip install boundflow\nexport ANTHROPIC_API_KEY=<your key>\n\n# 5. Run a real agent under governance\npython -m boundflow.examples.hello\n```\n\nThen explore the bundled examples:\n\n```\npython -m boundflow.examples.approval_gate   # human-in-the-loop sign-off\n```\n\nManage and observe it from the ** boundflow CLI** (installed with the SDK):\n\n```\nboundflow workflow list            # your workflows and their state\nboundflow workflow runs <id>       # runs and their outcomes  ·  --json for scripting\n```\n\nObservability is first-class and **OpenTelemetry-native** — no proprietary format,\nno lock-in, so it plugs straight into the telemetry stack you already run. Two\nlayers: **run traces** (execution telemetry you export to your own backend) and a\n**governance audit log** (decisions, kept server-side and queryable).\n\n**Run traces.** Every operation emits an `OperationTrace`\n\n— the `operation → agent → llm/tool`\n\ntree with token usage and full prompt/response content — to a pluggable\nsink you own. Built-ins: `LoggingTraceSink`\n\n, `JsonlFileTraceSink`\n\n, and\n`OTelTraceSink`\n\n, which maps onto OpenTelemetry GenAI semantic conventions and ships\nspans over OTLP to any backend (Jaeger, Tempo, Langfuse, Phoenix, …); all operations\nof one run share a `trace_id`\n\n.\n\n``` python\nfrom boundflow import BoundFlowWorker\nfrom boundflow.trace import OTelTraceSink\n\nworker = BoundFlowWorker(llm=..., trace_sink=OTelTraceSink(tracer))\n```\n\nSee [ sdk/python/examples/otel/](/boundflow/boundflow/blob/main/sdk/python/examples/otel) for a runnable\nOTLP → Jaeger setup.\n\n**Approval audit.** Approval decisions are governance, not telemetry, so the\ndecision / actor / timing live in a durable server-side audit log — the trace\ncarries only the `approval_id`\n\n(on the `await_approval`\n\nspan) as the correlation\nkey. Look the record up by that id:\n\n``` php\nrecords = await cp.get_approval_audit(approval_id=\"…\")\n# -> decision (approved | rejected | timed_out), actor, opened_at, decided_at\n```\n\n**Inventory.** `cp.list_workflows()`\n\nreturns every workflow with its current\nlifecycle / workflow state for dashboards.\n\nBackend and SDK are configured through `BOUNDFLOW_*`\n\nenvironment variables (plus\n`ANTHROPIC_API_KEY`\n\nfor real agents). See\n** docs/deployment.md** for the full reference and the\nTLS-termination setup.\n\nThe default Postgres credentials in the compose files (\n\n`boundflow/boundflow`\n\n) are forlocal development only— set real credentials before any non-local deployment, and don't publish the Postgres port.\n\n``` php\nmake build   # build the binary -> bin/boundflow\nmake test    # go test ./...\nmake proto   # regenerate gRPC stubs (Go + Python)\n```\n\nSee ** CONTRIBUTING.md** for full setup, the proto workflow, and\nrunning the Python SDK test suites. CI runs the Go + mock-LLM suites on every PR; a\nseparate live-LLM suite (real Anthropic calls) runs on demand.\n\nDon't want to run or manage the control plane yourself? **BoundFlow Cloud** is an\nearly-access managed deployment — same gRPC API, same `pip install boundflow`\n\nSDK.\nInference stays bring-your-own, so your Anthropic key and token spend remain yours;\nwe just run the control plane.\n\nIt's early and design-partner–oriented while we onboard the first users —\n** reach out** if you'd like in.\n\n**Backend**—[Apache-2.0](/boundflow/boundflow/blob/main/LICENSE).** Python SDK**(`sdk/python`\n\n) —[MIT](/boundflow/boundflow/blob/main/sdk/python/LICENSE).", "url": "https://wpnews.pro/news/show-hn-boundflow-an-open-source-control-plane-for-ai-agents", "canonical_source": "https://github.com/boundflow/boundflow", "published_at": "2026-07-11 21:07:11+00:00", "updated_at": "2026-07-11 21:35:32.235161+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "ai-infrastructure", "ai-safety", "ai-policy"], "entities": ["BoundFlow", "Anthropic", "Claude", "Haiku", "OpenTelemetry", "Jaeger", "Tempo", "Langfuse"], "alternates": {"html": "https://wpnews.pro/news/show-hn-boundflow-an-open-source-control-plane-for-ai-agents", "markdown": "https://wpnews.pro/news/show-hn-boundflow-an-open-source-control-plane-for-ai-agents.md", "text": "https://wpnews.pro/news/show-hn-boundflow-an-open-source-control-plane-for-ai-agents.txt", "jsonld": "https://wpnews.pro/news/show-hn-boundflow-an-open-source-control-plane-for-ai-agents.jsonld"}}