Persistent Governance Memory: Integrating Dakera with TealTiger

TealTiger governs what your agents can do. Dakera remembers what they did — and makes sure the most critical decisions survive longest. Three integration patterns with full Python and TypeScript examples.

Dakera
+
TealTiger
3
integration classes
0.95
DENY importance score
5
KG hops max depth
2
pip/npm installs

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

PATTERN 01
Cost Tracking
Persist every LLM call's token usage and cost via DakeraCostStorage. Query by agent, request, date range, or model. Supports all 8 CostStorage methods.
PATTERN 02
Decision Auditing
Store governance decisions with DakeraDecisionStore. Importance-weighted by action severity. Idempotency via is_terminal() guard before re-evaluation.
PATTERN 03
Delegation Chains
Track multi-agent delegation hierarchies with DakeraDelegationHelper. BFS graph traversal over delegated_from KG edges. Up to 5 hops depth.

Installation

Python
TypeScript
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:

Python
TypeScript
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.

Python
TypeScript
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.

Python
TypeScript
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:

Agent code
TealTiger middleware
LLM API
↓ on every request: policy evaluation, CostRecord emission, Decision emission
DakeraCostStorage
importance=0.7
DakeraDecisionStore
importance=0.80–0.95
DakeraDelegationHelper
KG edges
↓ persist to
Dakera memory engine
RocksDB + HNSW index
Decay scheduler
↓ queryable via
Batch recall (filter-by-tag)
+
KG traversal (BFS)
+
Hybrid recall (semantic)

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.

Python — full wiring
TypeScript — full wiring
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 };
See the integration in action
Try the Dakera memory API live — no account required
Open Playground

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.

Build with Dakera

Give your AI agents persistent memory — self-hosted, production-ready, zero dependencies.

Stay in the loop
Get Dakera updates — releases, guides, and benchmarks. No spam.
✓ Subscribed. Thanks!