Skip to content
Tracon

CompiledAgentCache

Namespace Tracon · Assembly Tracon.Core.dll

Caches compiled agents keyed by (tenant, name, definition fingerprint, dependency fingerprint, culture), holding one entry per (tenant, name, definition fingerprint, culture).

public sealed class CompiledAgentCache
Inheritance
object ← CompiledAgentCache
Inherited Members
object.GetType(), object.ToString(), object.Equals(object?), object.Equals(object?, object?), object.ReferenceEquals(object?, object?), object.GetHashCode()

The key measures CONTENT, not a version number. An earlier design keyed the entry by the definition’s version and relied on “a save increments the version, so the entry naturally goes stale”. That reasoning does not hold for a DELETE followed by a CREATE of the same name: a store that restarts numbering (the built-in ones did, until they kept the numbering) makes the new definition version 1 again, and the deleted definition’s compiled agent kept answering every run - the database read correctly while the run did not. Measured on a real host.

The version component was therefore replaced by a fingerprint of the definition’s own serialized content (AgentDefinitionCompiler.CreateDefinitionFingerprint). Two entries share a compiled agent only when the definitions that produced them are byte-identical, which is exactly when sharing is correct. No explicit invalidation is needed, and none exists: an entry that can no longer be reached is simply never read again.

The same rule covers what the agent depends on outside its definition. The dependency fingerprint measures skills, callable sub-agents, a shared instructions block, the tenant’s egress policy, and the connection each remote (MCP) tool is bound to: a policy saved after an agent was cached changes the key, and the next resolve compiles again under that policy; a server definition changed or deleted after an agent was cached changes the key too, and the next resolve binds the live connection or fails the way an uncached compile does.

The cache is bounded by definitions, not by dependency changes. The dependency fingerprint changes without any operator action: a remote MCP server that edits a tool’s description or schema changes it at the next refresh. Each (tenant, name, definition fingerprint, culture) therefore holds ONE entry, and a compile under a new dependency fingerprint replaces it; the replaced agent is never read again. The definition fingerprint stays a separate slot: two versions of one agent served side by side (an experiment’s variants) must not evict each other on every run. A new definition version still adds a slot, which only an operator’s save can produce.

The tenant component of the key is REQUIRED. Agent names are unique only within a tenant (see SqlAgentDefinitionStore) - it is common for two different tenants to have a definition with the same name (e.g. "support"), byte-identical content, and the same dependency fingerprint (an empty string for both if they use no skills/callable agents). If the tenant is not included in the key, resolution for the second tenant returns the first tenant’s compiled agent (instructions, tool bindings - including the tenant identity bound into e.g. search_knowledge): tenant isolation broken outright.

Number of entries in the cache: one per tenant, agent name, definition, and culture.

public int Count { get; }

int

Empties the cache entirely.

public void Clear()

Combines multiple dependency fingerprints into a single key component.

public static string CombineFingerprints(string first, string second)

first string

First fingerprint.

second string

Second fingerprint.

string

The combined fingerprint.

A separator is required: fingerprints may not have a fixed length, and a direct concatenation would let two different pairs produce the same string.

GetOrAdd(string, string, string, Func<AIAgent>)

Section titled “ GetOrAdd(string, string, string, Func<AIAgent>)”

Retrieves the agent from the cache; if absent, produces it with factory and adds it.

public AIAgent GetOrAdd(string tenantId, string name, string definitionFingerprint, Func<AIAgent> factory)

tenantId string

Identifier of the tenant requesting resolution.

name string

Agent name.

definitionFingerprint string

Fingerprint of the definition’s own content; see AgentDefinitionCompiler.CreateDefinitionFingerprint.

factory Func<AIAgent>

Producer called when absent from the cache.

AIAgent

The compiled agent.

ArgumentNullException

One of the parameters is null.

GetOrAdd(string, string, string, string, Func<AIAgent>)

Section titled “ GetOrAdd(string, string, string, string, Func<AIAgent>)”

Retrieves the agent from the cache together with its dependency fingerprint; if absent, produces it with factory and adds it.

public AIAgent GetOrAdd(string tenantId, string name, string definitionFingerprint, string dependencyFingerprint, Func<AIAgent> factory)

tenantId string

Identifier of the tenant requesting resolution.

name string

Agent name.

definitionFingerprint string

Fingerprint of the definition’s own content; see AgentDefinitionCompiler.CreateDefinitionFingerprint.

dependencyFingerprint string

Current fingerprint of what the compiled agent depends on outside its definition: skills, callable sub-agents, a shared instructions block, the tenant’s egress policy, and the connection each remote tool is bound to. These can change while the definition stays the same, so they need a separate key component.

factory Func<AIAgent>

Producer called when absent from the cache.

AIAgent

The compiled agent.

GetOrAdd(string, string, string, string, string, Func<AIAgent>)

Section titled “ GetOrAdd(string, string, string, string, string, Func<AIAgent>)”

Retrieves the agent from the cache together with its dependency fingerprint and requested culture; if absent, produces it with factory and adds it.

public AIAgent GetOrAdd(string tenantId, string name, string definitionFingerprint, string dependencyFingerprint, string culture, Func<AIAgent> factory)

tenantId string

Identifier of the tenant requesting resolution.

name string

Agent name.

definitionFingerprint string

Fingerprint of the definition’s own content; see AgentDefinitionCompiler.CreateDefinitionFingerprint.

dependencyFingerprint string

See CompiledAgentCache.GetOrAdd.

culture string

The culture the definition was compiled with (resolved against AgentDefinition.InstructionsByCulture). An empty string when the run requested no culture. Part of the key: two runs of the same definition in different cultures must not share a compiled agent - the requested instructions text is baked into AI.ChatOptions at compile time.

factory Func<AIAgent>

Producer called when absent from the cache.

AIAgent

The compiled agent.

GetOrAddAsync(string, string, string, string, Func<ValueTask<AIAgent>>)

Section titled “ GetOrAddAsync(string, string, string, string, Func<ValueTask<AIAgent>>)”

Retrieves the agent from the cache together with its dependency fingerprint; if absent, produces it asynchronously with factory and adds it.

public ValueTask<AIAgent> GetOrAddAsync(string tenantId, string name, string definitionFingerprint, string dependencyFingerprint, Func<ValueTask<AIAgent>> factory)

tenantId string

Identifier of the tenant requesting resolution.

name string

Agent name.

definitionFingerprint string

Fingerprint of the definition’s own content; see AgentDefinitionCompiler.CreateDefinitionFingerprint.

dependencyFingerprint string

See CompiledAgentCache.GetOrAdd.

factory Func<ValueTask<AIAgent>>

Producer called when absent from the cache.

ValueTask<AIAgent>

The compiled agent.

Compiling now needs an async credential lookup on a cache miss. Concurrent.ConcurrentDictionary has no native async GetOrAdd; the same “the factory may run more than once, production is side-effect free, an extra instance is discarded” guarantee as the sync overload applies here too.

GetOrAddAsync(string, string, string, string, string, Func<ValueTask<AIAgent>>)

Section titled “ GetOrAddAsync(string, string, string, string, string, Func<ValueTask<AIAgent>>)”

Retrieves the agent from the cache together with its dependency fingerprint and requested culture; if absent, produces it asynchronously with factory and adds it.

public ValueTask<AIAgent> GetOrAddAsync(string tenantId, string name, string definitionFingerprint, string dependencyFingerprint, string culture, Func<ValueTask<AIAgent>> factory)

tenantId string

Identifier of the tenant requesting resolution.

name string

Agent name.

definitionFingerprint string

Fingerprint of the definition’s own content; see AgentDefinitionCompiler.CreateDefinitionFingerprint.

dependencyFingerprint string

See CompiledAgentCache.GetOrAdd.

culture string

See CompiledAgentCache.GetOrAdd.

factory Func<ValueTask<AIAgent>>

Producer called when absent from the cache.

ValueTask<AIAgent>

The compiled agent.