Security & Guardrails
IntaGrin includes several concrete mechanisms for limiting cost, loop, and content risk in an agentic system — this page describes what each one actually does. See 08_Security_Audit.md for a dated third-party-style review of what's remediated and what still needs deployment-time mitigation (network egress, prompt-injection classification, etc.) — this framework reduces agentic risk, it doesn't eliminate the need for your own security review.
1. Engine-Level Circuit Breakers
In ai.yaml, you can define strict runtime boundaries to prevent runaway costs, infinite handoff loops, and hallucination spirals:
circuit_breakers:
max_handoffs_per_session: 10 # defaults to 25 if omitted — never unlimited
max_tool_failures_in_a_row: 3
max_usd_cost_per_session: 1.50
max_delegation_depth: 3 # defaults to 3
max_delegation_turns: 15 # defaults to 15If an agent exceeds these limits, the execution loop is instantly hard-killed. inta verify reports which of these are backed by static graph analysis (handoffs, deterministic routers) versus only a runtime cap (semantic auto_route, self-healing retries, memory compression) — read its output before assuming a given config is fully bounded.
2. Self-Healing Error Loop Compression
If an LLM hallucinates bad python code, the tool will throw an error. IntaGrin contains an advanced _compress_error_loops garbage collector. If an agent fails 3 times in a row with the exact same error, IntaGrin deletes the redundant history and injects a hard system boundary into the prompt: "YOU ARE STUCK IN A LOOP. Formulate a completely different approach."
When episodic_memory: is configured, the same trigger also records the failure pattern (tool name + exact error text) as a remember_episode entry (event_type="failure_pattern", tagged ["reflexion", <tool name>]) — a Reflexion-style connector between two features that already existed separately. Without it, this loop-breaker only ever helped the one session it fired in; the next session (or a different user hitting the same broken call) got no benefit from what was already learned. The system boundary message itself now points the model at recall_episodes(event_type='failure_pattern') so it can check for a prior encounter before retrying. No extra LLM call is spent generating the reflection — the raw failure pattern is itself a concrete, matchable fact, and this fires on a synchronous hot path called every turn-loop iteration.
3. Tenant Isolation (Anti-IDOR)
If you deploy IntaGrin behind a SaaS frontend, you must prevent User A from resuming User B's paused agent session. When custom authentication returns a stable tenant ID, the HTTP chat, streaming, resume, session-listing, monitor, and voice endpoints namespace their checkpoint IDs with that ID. Use custom authentication for multi-tenant deployments; none and api_key modes intentionally use the shared global_tenant namespace. Custom checkpointers remain responsible for applying the same isolation rule.
4. Trace-Based Swarm Evaluation (LLM-as-a-Judge)
Because IntaGrin flushes every turn to the SQLite checkpointer, you have a complete "flight data recorder" for every session. You can rigorously evaluate complex, multi-agent trajectories offline using the CLI:
inta eval --judge --session-id "sess_123"The framework retrieves the exact conversation trace from the database and feeds it to an LLM-as-a-judge to grade the swarm's efficiency and logical pathing.
5. Native Content Guardrails (PII & Toxicity)
Enterprise deployments cannot blindly pass user input to an LLM, nor can they blindly send LLM outputs to users. IntaGrin contains a native interceptor layer configured declaratively:
model:
primary: "openai/gpt-4o"
guardrails:
mask_pii: true
system_safeguards: true
banned_words: ["competitor_name", "ignore previous instructions"]mask_pii: Intercepts and redacts credit cards, emails, and SSNs before they leave your network.system_safeguards: Automatically injects strict boundary constraints into the agent's system prompt to prevent jailbreaks.- You can even define a
custom_module: "security.my_guardrails"(exposing anapply_guardrails(text, guardrails)function) to run custom Python interceptors — in addition tomask_pii/banned_wordsabove, not instead of them. Your custom function's return value is whatmask_pii/banned_wordsthen run against, so if it introduces new text (e.g. an error message quoting the input), that text is still subject to the built-in checks. A custom module that raises falls back to just the built-in checks for that call, never to no checks at all.
6. Typed State & Response Validation
Malformed or wrongly-typed data in shared state is a common source of downstream tool failures. Declare state_schema: "schemas.AppState" at the root and every write_state call is validated against that Pydantic model before it's committed — a violating write is rejected and the validation error is returned to the agent instead of corrupting state silently. Per-agent response_schema does the same for an agent's final structured output, with one self-heal retry via a corrector model before surfacing the validation failure.
7. OpenTelemetry (OTEL) Observability
Enterprise companies require tracing to debug LLM spans. IntaGrin integrates natively with OpenTelemetry (OTEL) and Langfuse. Simply add:
telemetry:
- "otel"
- "langfuse"Setting telemetry: ["otel"] turns on two independent, additive things:
LiteLLM's own instrumentation — W3C OpenTelemetry spans for every LLM interaction (model, tokens, latency, cost) to your APM of choice.
IntaGrin's own GenAI-semconv spans (
tracing/otel_exporter.py) — tool calls, router decisions (fired or not, including a condition that raised), handoffs, agent spawns/retirement, and MCP background-task lifecycle events, each mapped onto the OpenTelemetry GenAI semantic conventions (gen_ai.system,gen_ai.request.model,gen_ai.usage.input_tokens/output_tokens) where a standard attribute exists, and vendor-namespacedintagrin.*attributes for everything else. These are exported as a separate span tree from LiteLLM's own spans — not merged into one hierarchy — correlated only via sharedintagrin.session_id/service-name attributes, not a single trace.The exported
service.namedefaults to your project's ownai.yamlname(not a hardcoded"intagrin"), so several separately-deployed projects sharing one OTLP backend still show up as distinct services rather than one indistinguishable blob — useful the moment you're correlating traces across more than one deployment.
Both default to printing spans to the console (via the core opentelemetry-sdk dependency, no extra install needed). Set the standard OTEL_EXPORTER_OTLP_ENDPOINT environment variable to export real OTLP instead — this single variable controls both exporters at once. Real OTLP export requires the optional intagrin[otel] extra (pip install "intagrin[otel]"); setting the endpoint without it raises IG-RT-009 rather than silently falling back to console output.
inta monitor's live SSE dashboard and inta replay's post-hoc session view remain available independent of telemetry: — they don't require OTel at all.
8. Lethal-Trifecta Guardrail (Untrusted Content Tracking)
The riskiest shape a prompt-injection attack takes isn't the injection itself — it's the combination of untrusted content + access to private data/state + a way to exfiltrate it, sometimes called the "lethal trifecta." IntaGrin can't detect injection itself, but it tracks provenance so you can structurally break that combination:
tools:
- name: "search_docs"
type: "mcp" # untrusted_output defaults to true for mcp/openapi/sandbox
command: "npx"
args: ["-y", "docs-mcp-server"]
- name: "send_customer_email"
module: "tools.email"
requires_approval: true
available_when: "not _untrusted_content_ingested"The moment any tool call flagged untrusted_output: true succeeds (RAG's search_knowledge_base, and MCP/OpenAPI/sandbox tools by default), state["_untrusted_content_ingested"] is set for the rest of the session — sticky, never reset, since the content stays in conversation history regardless. Any available_when or routers[].condition can reference that bare state key directly, no extra config needed. inta verify advises (never fails) when an agent has both an untrusted-output tool and a separate requires_approval tool that doesn't reference the key.
9. Sandboxed Code Execution
type: "sandbox" tools (see 03_Tools_and_Actions.md) default to backend: "local": agent-generated code in an isolated subprocess with a wall-clock timeout, POSIX CPU/memory limits, and a secret-free environment — reducing blast radius for a buggy or runaway script. Read that as exactly what it is: process and resource isolation, not a filesystem or network security boundary. For genuinely untrusted code, combine requires_approval: true with the lethal-trifecta guardrail above, or set backend: "e2b" to run it in a real Firecracker microVM instead, with genuine filesystem/network isolation (see 03_Tools_and_Actions.md's "Real isolation via E2B" section).
