Why Tulip¶
Tulip is a complete open-source agent framework with an explicit enforcement
point for consequential actions. Its useful promise is narrow and testable:
a callable routed through admit() is invoked only after the supplied policy
allows its declared Action. The runtime can instead hold the call for a
person or deny it, and it can record that decision.
This is stronger than a prompt instruction, but it is not a claim that every possible action is automatically safe. Coverage depends on how an application classifies actions and whether all side-effecting paths use the gate.
Control at three moments¶
| Moment | Tulip surface | What it does |
|---|---|---|
| Decide what happens next | Agent loop | Runs bounded reasoning, tool calls, observations, and termination |
| Assess what may be asserted | GSAR | Scores a judge-produced, typed evidence partition and supports proceed, regenerate, replan, or abstain behavior |
| Decide whether an action executes | Admission | Evaluates an Action against ControlPolicy before calling the side effect |
These controls compose, but none silently enables the others. Grounding and admission are configuration choices, and a high-stakes deployment should make those choices visible in code and tests.
The admission guarantee¶
try:
result = await admit(
action,
perform,
policy=policy,
trail=trail,
)
except AdmissionError as exc:
handle_refusal(exc.decision)
Within that call:
performruns only for an allowed decision;- a hold or denial does not invoke
perform; - the decision is appended when an
AuditTrailis supplied.
The guarantee does not extend to another reference that calls the underlying
function directly. gate_tool() makes the gated wrapper the tool exposed to
an agent, but application code and review must still prevent bypass paths.
Runtime, configuration, and judgment¶
| Capability | Runtime enforces automatically | Developer configures | Remains judgment-dependent |
|---|---|---|---|
| Policy gate | Deterministic evaluation and pre-execution allow/hold/deny | Action labels, rules, gate coverage, approval handling | Whether the classification captures the actual consequence |
| Human approval | Approval state and one-use consumption in the configured store | Durable storage, authenticated approver, resume workflow | Whether the human has enough context to decide |
| GSAR grounding | Score and threshold logic over a typed partition | Evidence corpus, judge, thresholds, recovery/fail behavior | The judge's claim and evidence assessment |
| Typed findings | Carries an explicit score, references, and provenance fields | Criteria for emitting or accepting the finding | Whether source evidence is complete and correctly interpreted |
| Idempotent tools | Reuses a cached result for an identical call in the documented scope | Tool annotation, checkpoints, downstream operation key | Semantic equivalence of differently encoded calls |
| Audit trail | Hash-chains appended records and detects later edits to that chain | Durable export, access controls, external anchoring | Whether all relevant events were sent to the trail |
Compared with prompt rules and filters¶
| Prompt instruction | Input/output filter | Tulip admission gate | |
|---|---|---|---|
| Evaluated where | Model context | Around model text | Around a specific callable |
| Can block the side effect itself | No | Only if application wiring makes it authoritative | Yes, for calls routed through admit() |
| Depends on model compliance | Yes | Often, when classification is model-based | Policy evaluation does not; action classification still can |
| Human hold | Application-specific | Framework-specific | Built-in stores and resume flow |
| Decision record | Usually logs | Usually logs | Optional hash-chained AuditTrail |
Guardrails remain useful for prompt injection, content policy, and data-loss prevention. Admission solves a different problem: making an application-level decision before a named side effect runs.
Grounding is an assessment, not truth¶
GSAR makes the evidence assessment inspectable: claims are assigned to grounded, ungrounded, contradicted, or complementary partitions, then scored. Threshold logic is deterministic once that partition exists. Producing the partition is normally an LLM judgment and can be wrong. Evaluate the judge on your domain, calibrate thresholds, and choose whether low scores cause a rewrite, replan, abstention, or hard failure.
A typed finding therefore means “this object carries an explicit evidence assessment,” not “this claim cannot be wrong.”
Idempotency is scoped¶
@tool(idempotent=True) deduplicates matching calls within the documented
agent/checkpoint scope. It does not by itself close the crash window where an
external service succeeds and the local result has not yet been durably
recorded. For payments, deploys, and messages, also send a stable idempotency
key to the downstream API.
When Tulip is a good fit¶
Tulip earns its control layer when an agent can move money, change infrastructure, alter access, contact a third party, or create another durable effect. A read-only summarizer may need grounding and evaluation without an admission gate.
Start with the first agent, then run the first controlled action. Before production, read policy authoring, idempotency, and the deployment guide.