Skip to content

beomwookang/deliberate

Repository files navigation

Deliberate — The approval layer for LangGraph agents

Quickstart · Docs · Discord · Report a bug

MIT License GitHub Stars

Your LangGraph agent calls interrupt(). Now what?

Deliberate turns it into an approval request a non-engineer can actually answer — with notifications, timeouts, structured audit trails, and UIs built for the people who actually sign off.


The gap LangGraph leaves open

LangGraph made interrupt() a first-class primitive. That solved the runtime half of human-in-the-loop — the graph pauses, state is checkpointed, and execution resumes with Command(resume=...).

The organizational half is still yours to build:

  • Nobody gets notified. The thread sits in the checkpointer. If your approver isn't watching the terminal, they don't know you're waiting on them.
  • Nothing times out. A graph paused 3 weeks ago is indistinguishable from one paused 30 seconds ago.
  • There's no audit log. State is persisted, but who decided, when, why, with what context — that record doesn't exist unless you build it.
  • Your approver is probably not an engineer. But the only interface LangGraph gives them is your Python REPL.

Deliberate fills that gap — everything between your agent paused and your agent resumes.


Quickstart

1. Run Deliberate

git clone https://github.com/beomwookang/deliberate.git
cd deliberate
docker compose up

Deliberate is running on http://localhost:4000.

2. Install the SDK (and optionally the MCP server)

pip install deliberate

# Optional: connect Claude Code / Cursor to your Deliberate instance
pip install deliberate-mcp
# or: uvx deliberate-mcp (no install required)

3. Wrap your LangGraph node

from deliberate import approval_gate
from langgraph.types import interrupt

@approval_gate(
    layout="financial_decision",
    notify=["email:[email protected]", "slack:#finance-approvals"],
    policy="policies/refund.yaml",
)
def process_refund(state):
    return interrupt({
        "customer": state.customer,
        "amount": state.amount,
        "agent_reasoning": state.reasoning,
        "evidence": state.evidence,
    })

That's it. When the graph reaches this node, Deliberate routes the approval, notifies the right person, and waits — then hands the decision back to your graph.

See the full quickstart or grab a working example.


Built-in layouts for HITL-critical domains

Different decisions need different information architecture. A finance lead approving a refund doesn't need the same layout as a legal reviewer redlining a contract. Deliberate ships with 6 layouts tuned for the domains where HITL matters most.

financial_decision
Refunds, expense approvals, budget requests.

financial_decision layout
document_review
Contracts, policies, legal redlines.

document_review layout
procedure_signoff
Audit steps, compliance checks, quality gates.

procedure_signoff layout
data_access
Sensitive data access, export approvals.

data_access layout
content_moderation
Content review, publish approvals.

content_moderation layout
code_deployment
Automated deploys and infra changes.

code_deployment layout

Need something else? Build a custom layout — layouts are just React components that consume a typed payload schema.


Agent-Friendly

Deliberate is designed to be managed by AI coding assistants, not just humans editing YAML files.

  • Admin REST API — Full CRUD for policies, approvers, groups, and API keys. Test a policy against a sample payload before deploying it. See Admin API Reference.
  • MCP Serverpip install deliberate-mcp or uvx deliberate-mcp. Connects Claude Code, Cursor, and Windsurf directly to your Deliberate instance. 17 tools covering every admin operation. See MCP Server Guide.
  • Scoped RBAC — 11 scopes, 4 predefined roles (agent, readonly, operator, admin), API key format dlb_ak_*. See RBAC Guide.
  • llms.txt — Machine-readable project summary at /llms.txt for LLM context windows.
  • OpenAPI — Interactive docs at GET /docs, machine-readable spec at GET /openapi.json.

Features

  • Multi-channel notifications — Slack, Email, or Webhook. Pick one, or fan out to all three.
  • Timeouts and escalation — Approver didn't respond in 4h? Auto-escalate to the backup or fail gracefully.
  • YAML policy routing — Declare who approves what based on payload. Auto-approve small amounts, require two sign-offs for big ones.
  • Admin REST API — Manage policies, approvers, groups, and API keys programmatically. No YAML editing required.
  • Audit ledger — Append-only, hash-chained, with JSON/CSV export. Every decision structured and tamper-evident.
  • OTLP export — Feed ledger events into Langfuse, Phoenix, or any OTLP-compatible collector.
  • Prometheus metrics/metrics endpoint with interrupts, decisions, duration, timeouts, escalations.
  • Approver identity — Magic link email verification. No more anonymous@ in your audit trail.
  • Mobile-first approver UI — Because your finance lead approves from their phone in meetings.
  • LangGraph native — Built directly on interrupt() and Command(resume=...). No adapters, no abstractions to learn.

How it works

sequenceDiagram
    participant Agent as LangGraph Agent
    participant Server as Deliberate Server
    participant Approver

    Agent->>Server: interrupt() + @approval_gate
    Server->>Server: Evaluate YAML policy
    Server->>Approver: Notify (Slack / Email / Webhook)
    Approver->>Server: Open signed approval link
    Approver->>Server: Submit structured decision
    Server->>Server: Write to append-only ledger
    Server->>Agent: Command(resume=decision)
Loading
  1. The SDK captures the payload and posts it to Deliberate's server.
  2. Deliberate evaluates your YAML policy to resolve approvers, timeout, and escalation rules.
  3. Notifications fire to the configured channels with a signed JWT approval link.
  4. The approver opens the link, sees the layout you configured, and submits a structured decision.
  5. Deliberate writes the decision to the append-only ledger and resumes your graph.
  6. Your agent picks up exactly where it paused.

The graph state lives in LangGraph's checkpointer. The approval state, audit trail, and policy evaluation live in Deliberate's Postgres. The two stay synchronized through the LangGraph thread ID.


Relationship to LangGraph

Deliberate isn't a replacement for LangGraph's HITL primitives — it builds on them.

LangGraph gives you interrupt(), Command(resume=...), and a checkpointer. That's the low-level runtime, and it's excellent. What LangGraph deliberately doesn't ship (by design, since every team's notification stack and audit requirements are different) is the layer that turns an interrupted thread into an actual request a human sees, responds to, and leaves a record of.

That layer is Deliberate. Use LangGraph's interrupt() anywhere — Deliberate only kicks in when you wrap a node with @approval_gate. Mix and match as you like.


Project status

Deliberate is at v0.1.0 (pre-release). The core flow (SDK → server → notifications → approval UI → resume → ledger) works end-to-end. Self-hosting is supported. Managed cloud is not yet available.

Documentation

Not planned (for now)

  • Support for non-LangGraph agent frameworks. Our goal is to be the best approval layer for LangGraph specifically.
  • BPMN-style multi-step workflows. If you need this, check out Camunda or Temporal.

Related projects

  • LangGraph — the agent runtime Deliberate builds on.
  • Langfuse — LLM observability and tracing. Complementary: Langfuse records what the agent did; Deliberate records what the human decided.
  • OpenTelemetry GenAI conventions — the spec we align ledger exports to.

Contributing

We welcome contributions. Start here:

Good first issues are tagged good-first-issue.


License

MIT. See LICENSE.

About

Approval layer for LangGraph agents — human-in-the-loop decisions with policy engine, notifications, and multi-approver workflows

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors