AI agents are making decisions at machine speed — approving requests, routing tasks, denying access, redacting data. Most teams instrument governance on the way in. Almost nobody persists it on the way out. When an auditor asks "what did your agent decide, and why?" the answer is usually a log file that may or may not still exist.
This is the gap that the Dakera + TealTiger integration closes. TealTiger governs what agents can do — intercepting LLM calls, enforcing policies, emitting structured decision records. Dakera persists those records as decay-weighted memories, ensuring that high-severity decisions survive compaction longer than routine allows. Together, they form a layered compliance stack: governance at the request layer, persistent memory at the infrastructure layer.
Complementary, not competing. TealTiger is a governance middleware — it wraps your LLM client and enforces policies. Dakera is a memory server — it stores, retrieves, and ages information. Neither does what the other does. This integration wires them together so governance artefacts (cost records, decision receipts, delegation chains) are automatically persisted, queryable, and auditable.
Why governance data needs persistent memory
In-memory governance stores (the default for most frameworks) have a fundamental lifecycle problem: they exist only as long as the process does. Restart the agent, and the audit trail disappears. Scale horizontally across instances, and each replica has its own isolated view. Run a post-mortem three days after an incident, and the data is gone.
Persistent storage addresses the lifecycle problem but introduces a new one: equal retention. A routine ALLOW decision and a security-critical DENY decision consume the same storage slot and get deleted on the same schedule. Compliance doesn't work that way — high-severity records need to outlast low-severity ones.
Dakera's importance-weighted decay model solves this naturally. Every memory stored in Dakera has an importance score between 0 and 1. The decay engine uses this score to calculate a half-life: high-importance memories decay slowly, low-importance ones decay faster. The Dakera + TealTiger integration maps TealTiger's DecisionAction enum directly to Dakera importance scores, so the retention priority is built into the data from the moment it's stored:
| TealTiger Action | Severity | Dakera importance | Retention behavior |
|---|---|---|---|
| DENY | Critical | 0.95 | Longest retention — survives maximum compaction rounds |
| REQUIRE_APPROVAL | High | 0.90 | Near-maximum — pending decisions stay queryable |
| REDACT | High | 0.90 | Near-maximum — PII redactions are compliance-grade |
| TRANSFORM | Medium | 0.85 | Extended retention — content modifications tracked |
| DEGRADE | Medium | 0.85 | Extended retention — quality degradations tracked |
| ALLOW | Routine | 0.80 | Standard retention — routine permits are still auditable |
This table isn't a configuration file you fill in — it's the default behavior, encoded in the integration source code. You don't have to think about retention tiers. You just call store_receipt() and the right importance score is chosen automatically based on the decision action.
Three integration patterns
DakeraCostStorage. Query by agent, request, date range, or model. Supports all 8 CostStorage methods.DakeraDecisionStore. Importance-weighted by action severity. Idempotency via is_terminal() guard before re-evaluation.DakeraDelegationHelper. BFS graph traversal over delegated_from KG edges. Up to 5 hops depth.Installation
pip install "dakera[tealtiger]" tealtiger
npm install @dakera-ai/dakera tealtiger
The dakera[tealtiger] extra installs no new dependencies on the Dakera side — the TealTiger types are detected at import time via a soft optional import. Both packages work independently; the integration glue is in the Dakera SDK itself.
Pattern 1: Cost tracking with DakeraCostStorage
Every LLM call routed through TealTiger emits a CostRecord — model, provider, tokens, cost, and a stable request_id. DakeraCostStorage implements the CostStorage ABC so you can drop it directly into TealOpenAIConfig or TealAnthropicConfig. All eight storage methods are async:
import asyncio
from dakera.async_client import AsyncDakeraClient
from dakera.integrations.tealtiger import DakeraCostStorage
async def main():
client = AsyncDakeraClient(
base_url="http://localhost:3300",
api_key="dk-your-key",
)
# DakeraCostStorage namespaces all records under "governance"
cost_storage = DakeraCostStorage(client, dakera_agent_id="governance")
# Plug into TealTiger — every LLM call is automatically tracked
from tealtiger import TealOpenAI, TealOpenAIConfig
teal_client = TealOpenAI(
config=TealOpenAIConfig(cost_storage=cost_storage)
)
# teal_client.chat.completions.create(...) now persists cost records
# Query by agent over a date range
import datetime
records = await cost_storage.get_by_agent_id(
agent_id="research-agent",
start_date=datetime.datetime(2026, 6, 1, tzinfo=datetime.timezone.utc),
)
print(f"Found {len(records)} cost records for research-agent")
# Spend summary across all agents
summary = await cost_storage.get_summary(
start_date="2026-06-01",
end_date="2026-06-30",
)
print(f"Total spend this month: ${summary.total_cost:.4f}")
print(f"By model: {summary.by_model}")
asyncio.run(main())
import { DakeraClient } from '@dakera-ai/dakera';
import { DakeraCostStorage } from '@dakera-ai/dakera/integrations/tealtiger';
import { TealOpenAI, TealOpenAIConfig } from 'tealtiger';
const client = new DakeraClient('http://localhost:3300', {
apiKey: 'dk-your-key',
});
// All cost records stored in "governance" namespace
const costStorage = new DakeraCostStorage(client, 'governance');
// Plug into TealTiger — every LLM call is automatically tracked
const tealClient = new TealOpenAI({
config: new TealOpenAIConfig({ costStorage }),
});
// tealClient.chat.completions.create(...) now persists cost records
// Query by agent over a date range
const records = await costStorage.getByAgentId(
'research-agent',
new Date('2026-06-01'),
);
console.log(`Found ${records.length} cost records`);
// Spend summary across all agents
const summary = await costStorage.getSummary('2026-06-01', '2026-06-30');
console.log('Total spend: $' + summary.total_cost.toFixed(4));
console.log('By model:', summary.by_model);
Cost records are tagged with model:, provider:, agent:, and request_id: prefixes. This means Dakera's filter-based recall (batch_recall) can retrieve them without a full-table scan — the tags act as indexed keys. All eight standard CostStorage methods are implemented: store, get, get_by_request_id, get_by_agent_id, get_by_date_range, get_summary, delete_older_than, and clear.
Pattern 2: Decision auditing with DakeraDecisionStore
When TealTiger evaluates a request against a policy, it emits a Decision object: the action taken (DENY, ALLOW, REDACT, etc.), a correlation_id that ties decisions to the original request, and a policy_id identifying which rule fired. DakeraDecisionStore persists these as episodic Dakera memories with severity-weighted importance.
Why episodic memory type? Dakera supports four memory types: episodic, semantic, procedural, and working. Governance decisions are episodic — they happened at a specific moment in time, in response to a specific event. Episodic memories are indexed with timestamps and participate in temporal reasoning, which means you can query "what did the agent decide about PII last Tuesday?" without full-text scanning.
from dakera.integrations.tealtiger import DakeraDecisionStore
decision_store = DakeraDecisionStore(client)
# TealTiger emits a Decision after evaluating a request.
# decision.action = DecisionAction.DENY → importance 0.95
# decision.action = DecisionAction.ALLOW → importance 0.80
mem_id = await decision_store.store_receipt(
agent_id="research-agent",
decision=decision, # TealTiger Decision object
)
print(f"Decision stored: {mem_id}")
# Idempotency guard — check before re-evaluating the same request.
# REQUIRE_APPROVAL is NOT terminal (pending state).
# ALLOW, DENY, TIMED_OUT are terminal.
already_decided = await decision_store.is_terminal(
agent_id="research-agent",
correlation_id=decision.correlation_id,
)
if already_decided:
print("Request already has a terminal decision — skipping re-evaluation")
else:
print("No terminal decision yet — proceed with policy evaluation")
# Look up a specific decision by correlation ID
receipt = await decision_store.lookup_receipt(
agent_id="research-agent",
correlation_id=decision.correlation_id,
)
print(f"Decision action: {receipt.action}")
import { DakeraDecisionStore } from '@dakera-ai/dakera/integrations/tealtiger';
const decisionStore = new DakeraDecisionStore(client);
// Store a TealTiger Decision receipt.
// Importance is set automatically from decision.action:
// DENY → 0.95, REQUIRE_APPROVAL/REDACT → 0.90, TRANSFORM/DEGRADE → 0.85, ALLOW → 0.80
const memId = await decisionStore.storeReceipt('research-agent', decision);
console.log(`Decision stored: ${memId}`);
// Idempotency guard — ALLOW, DENY, TIMED_OUT are terminal.
// REQUIRE_APPROVAL is NOT terminal (it's a pending state).
const alreadyDecided = await decisionStore.isTerminal(
'research-agent',
decision.correlation_id,
);
if (alreadyDecided) {
console.log('Terminal decision exists — skip re-evaluation');
} else {
console.log('No terminal decision — proceed with policy evaluation');
}
// Retrieve by correlation ID
const receipt = await decisionStore.lookupReceipt(
'research-agent',
decision.correlation_id,
);
console.log('Decision action:', (receipt as Record<string, unknown>)['action']);
The is_terminal() / isTerminal() method is designed for event-driven pipelines where the same correlation ID might arrive twice. Checking this before policy evaluation prevents double-billing or conflicting decisions on retries. REQUIRE_APPROVAL is explicitly excluded from the terminal set — it's a pending state, not a final verdict. Only ALLOW, DENY, and TIMED_OUT are terminal.
Pattern 3: Delegation chains with DakeraDelegationHelper
Multi-agent systems often have one agent delegating to another: an orchestrator spawns a research sub-agent, which spawns a data-fetching sub-agent. When any of these agents make governance decisions, you need to understand the full delegation hierarchy to answer "who authorized this?" at audit time.
DakeraDelegationHelper creates typed delegated_from edges in Dakera's knowledge graph between decision memory nodes. This turns a set of isolated decision receipts into a traversable delegation tree. BFS graph traversal up to 5 hops deep lets you reconstruct the full authorization chain from any node.
from dakera.integrations.tealtiger import DakeraDecisionStore, DakeraDelegationHelper
decision_store = DakeraDecisionStore(client)
delegation_helper = DakeraDelegationHelper(client)
# Store the orchestrator's decision receipt
orch_mem_id = await decision_store.store_receipt(
agent_id="orchestrator",
decision=orchestrator_decision,
)
# Store the sub-agent's decision receipt
sub_mem_id = await decision_store.store_receipt(
agent_id="research-agent",
decision=sub_agent_decision,
)
# Link: research-agent's decision was delegated FROM orchestrator
await delegation_helper.link_delegation(
child_id=sub_mem_id,
parent_id=orch_mem_id,
)
# At audit time: reconstruct the full chain from the orchestrator's decision
chain = await delegation_helper.get_delegation_chain(
agent_id="orchestrator",
decision_id=orch_mem_id,
max_depth=5,
)
print(f"Delegation chain ({len(chain)} nodes): {chain}")
# → ['orch-mem-id', 'sub-mem-id', ...]
import { DakeraDecisionStore, DakeraDelegationHelper } from '@dakera-ai/dakera/integrations/tealtiger';
const decisionStore = new DakeraDecisionStore(client);
const delegationHelper = new DakeraDelegationHelper(client);
// Store orchestrator's decision receipt
const orchMemId = await decisionStore.storeReceipt('orchestrator', orchestratorDecision);
// Store sub-agent's decision receipt
const subMemId = await decisionStore.storeReceipt('research-agent', subAgentDecision);
// Link: sub-agent's decision was delegated FROM orchestrator
await delegationHelper.linkDelegation({
childId: subMemId,
parentId: orchMemId,
});
// Reconstruct the full chain at audit time
const chain = await delegationHelper.getDelegationChain(
'orchestrator',
orchMemId,
5, // max hops (clamped to 5 by KG API)
);
console.log(`Chain (${chain.length} nodes):`, chain);
// → ['orch-mem-id', 'sub-mem-id', ...]
The delegation chain is implemented using Dakera's knowledge graph edge API — the same KG that powers entity traversal in the rest of the memory system. The delegated_from edge type is typed and stored in the KG sidecar alongside the main RocksDB memory store. BFS traversal is capped at 5 hops by the KG API; passing a higher max_depth silently uses 5.
Architecture: how the layers fit together
TealTiger (github.com/agentguard-ai/tealtiger, Apache 2.0) provides the governance interception layer. Dakera provides the memory persistence layer. The two components are independently deployable — you can adopt either one without the other, and the integration is purely additive:
TealTiger sits at the request layer — every outbound LLM call passes through it, and TealTiger decides what to do: let it through, block it, modify it, or queue it for approval. The Dakera integration hooks into TealTiger's output (cost records and decision receipts) and routes them to the Dakera memory engine for persistent storage.
The Dakera memory engine stores records in RocksDB with an HNSW vector index for semantic retrieval. The decay scheduler runs a background half-life calculation on every memory, using the importance score to determine how quickly it ages. Because DENY decisions get importance 0.95 and ALLOW decisions get 0.80, the compaction engine naturally preserves security-critical records longer without any manual policy configuration.
Why this architecture matters for compliance
Most agent compliance stories assume that governance logs are append-only and kept forever. In practice, storage costs and data retention policies force teams to set expiry windows. When a DENY decision and an ALLOW decision both expire after 90 days, you lose the exact records that matter most at audit time.
The importance-based decay model gives you a lever that traditional log stores don't: you can express retention priority as a property of the data, not a property of the storage layer. A DENY decision stored with importance 0.95 will survive more compaction cycles than an ALLOW stored at 0.80 — automatically, without a separate retention policy engine.
The compliance audit path. An auditor asks: "Show me every governance decision your research agent made in June that resulted in a DENY, and which orchestrator delegated to it." With this integration, that's two Dakera queries: a batch_recall filtered by tags=["governance", "decision:deny"] with a date range, followed by knowledge_query on the delegated_from edges. The data is already there — persisted at decision time, with the delegation chain already wired into the knowledge graph.
Getting started
You need a running Dakera instance and TealTiger installed. If you don't have a Dakera server yet, the quickstart guide gets you to a running instance in five minutes via Docker. The playground at dakera.ai/playground lets you verify the memory API is working before wiring in governance.
import asyncio, os
from dakera.async_client import AsyncDakeraClient
from dakera.integrations.tealtiger import (
DakeraCostStorage,
DakeraDecisionStore,
DakeraDelegationHelper,
)
from tealtiger import TealOpenAI, TealOpenAIConfig
async def setup_governance():
client = AsyncDakeraClient(
base_url=os.environ["DAKERA_URL"],
api_key=os.environ["DAKERA_API_KEY"],
)
cost_storage = DakeraCostStorage(client, dakera_agent_id="governance")
decision_store = DakeraDecisionStore(client)
delegation_helper = DakeraDelegationHelper(client)
# Wire cost tracking into TealTiger
teal_client = TealOpenAI(
config=TealOpenAIConfig(cost_storage=cost_storage)
)
# On each TealTiger policy callback, persist the decision receipt:
async def on_decision(agent_id: str, decision):
mem_id = await decision_store.store_receipt(agent_id, decision)
return mem_id
# On each delegation event, link the chain:
async def on_delegation(child_mem_id: str, parent_mem_id: str):
await delegation_helper.link_delegation(
child_id=child_mem_id,
parent_id=parent_mem_id,
)
return teal_client, on_decision, on_delegation
asyncio.run(setup_governance())
import { DakeraClient } from '@dakera-ai/dakera';
import {
DakeraCostStorage,
DakeraDecisionStore,
DakeraDelegationHelper,
} from '@dakera-ai/dakera/integrations/tealtiger';
import { TealOpenAI, TealOpenAIConfig } from 'tealtiger';
const client = new DakeraClient(process.env.DAKERA_URL!, {
apiKey: process.env.DAKERA_API_KEY!,
});
const costStorage = new DakeraCostStorage(client, 'governance');
const decisionStore = new DakeraDecisionStore(client);
const delegationHelper = new DakeraDelegationHelper(client);
// Wire cost tracking into TealTiger
const tealClient = new TealOpenAI({
config: new TealOpenAIConfig({ costStorage }),
});
// On each TealTiger policy callback, persist the decision receipt:
async function onDecision(agentId: string, decision: unknown): Promise<string> {
return decisionStore.storeReceipt(agentId, decision);
}
// On each delegation event, link the chain:
async function onDelegation(childMemId: string, parentMemId: string): Promise<void> {
await delegationHelper.linkDelegation({ childId: childMemId, parentId: parentMemId });
}
export { tealClient, onDecision, onDelegation };
What's next
The Dakera + TealTiger integration ships in Python SDK v0.12.3+ (dakera[tealtiger]) and JavaScript SDK v0.11.94+ (@dakera-ai/dakera). Both SDKs are MIT-licensed. The Dakera memory engine is proprietary and self-hosted — no cloud dependency, no data leaves your infrastructure.
For production deployments, the quickstart covers Docker and binary installation. The TealTiger integration reference documents all three classes, their method signatures, and the tag schema used for indexed retrieval. For MCP-connected agents (Claude Desktop, Cursor, Windsurf), the MCP server guide covers how Dakera surfaces memory tools to your AI client directly.
Persistent governance for your agent stack
TealTiger governs your agents. Dakera remembers what they decided — with the most critical records surviving longest. Start with a running Dakera instance in under five minutes.