tulip agents · the agent framework where control is native
Agents that act. Safe by construction.¶
Tulip is a complete open-source agent framework — one Agent class, tools, durable
memory, RAG, eight multi-agent shapes, streaming, typed events — with one hard rule:
the model never holds the trigger. The agent decides to act — issue the refund, ship
the deploy, change the account — and the action runs only after your policy clears it,
in code the model can't reach.
The breadth is why the rule holds. You can only choose the shape, check the claim, and gate the action if you own the loop all three happen in — so Tulip ships the whole loop. See the framework surface.
from tulip import Agent, tool
@tool
def search_flights(
origin: str, dest: str, date: str
) -> list[dict]:
"Find flights between two cities."
return flights.search(origin, dest, date)
# A model is a string; a tool is a function.
agent = Agent(
model="anthropic:claude-sonnet-4-6",
tools=[search_flights],
system_prompt="You are a travel agent.",
)
print(agent.run_sync(
"Cheapest flight Lisbon to Berlin Friday?"
).text)
Control in the core¶
A model can be brilliant and still be talked into the wrong action. That's a control problem, not an intelligence problem — so Tulip puts the control in code the model can't reach:
- The agent loop is where the work happens — reason, act, observe, repeat — with reflexion, grounding, interrupts and budgets built in.
- GSAR scores every claim against typed evidence — below threshold the agent regenerates or abstains, never guesses.
- The admission gate clears every side-effecting call:
admit()allows it, holds it for a human, or denies it — and records the decision either way, on a trail where editing any entry breaksverify().
from tulip.control import (
Action, AuditTrail, ControlPolicy, admit, AdmissionError,
)
policy = ControlPolicy(require_human_for={"production"})
trail = AuditTrail()
async def safe_refund(order_id: str, usd: float):
try: # the gate runs before money moves
return await admit(
Action(name="refund", asset=order_id,
kind="payment", environment="production"),
lambda: payments.refund(order_id, usd),
policy=policy, trail=trail,
)
except AdmissionError:
return "Held for a human — not run."
A prompt rule is advisory — the model can be argued out of it. The gate is structural — the wrong action isn't caught in a filter, it never runs. How this compares to prompt rules and guardrails →
What you get¶
-
One
Agentclass — tools, memory, RAG, streaming — over vendor-neutral backends. Swap models with a string. -
fan_out,debate,plan_and_verify,code_until_tests_pass— the loop calls them when it has a reason to, not before it starts. -
Sequential, parallel, loop, graph, orchestrator, swarm, handoff, and cross-process A2A — one
Agentclass, one event stream. -
ground_finding()emits a typed result only above the GSAR threshold — else an auditableAbstention, never a guess. -
require_human_forpauses the actions that matter and resumes on a human's decision. Approvals survive restarts. -
Every call, verdict, and approval is a typed, hash-chained event —
verify()fails on any edit. Replay any run.
Build it across any domain¶
Every example is a single self-contained file under examples/ with a
matching docs page.
| You're building… | Start here |
|---|---|
| A support / ops agent that acts | human-in-the-loop approvals · incident response |
| An agent on your own data (RAG) | RAG basics · RAG agents |
| A multi-agent workflow | swarm / war-room · supervisor + critic |
| An agent that acts on approval | procurement approval · human-in-the-loop |
| A security / AI-safety agent | GSAR grounding · injection guardrails |
Full catalog → Notebooks index · Capabilities matrix · API reference
When Tulip is overkill¶
If your agent only reads and summarizes, you may not need an admission gate yet — the control layer earns its keep the moment an action can cost something.
Start building¶
The open-source agentic harness — control the action, prove what it did. Safe by construction. Apache-2.0.