AgentComposite
The AgentComposite is the complete definition of an Octopus AI agent — its LLM configuration, memory settings, tool registry, plugin list, system prompt, persona, and behavioral constraints. It is the blueprint from which every conversation is initiated.
LLM Configuration
Each agent is bound to an LLM configuration that specifies which model to use, the temperature, and the maximum context window size. This is stored as a LLMConfig value object:
public class LLMConfig
{
public string Provider { get; set; } // "Anthropic", "OpenAI", "AzureOpenAI", "Ollama"
public string Model { get; set; } // "claude-sonnet-4-6", "gpt-4o", etc.
public int CredentialId { get; set; } // resolved via ICredentialResolver
public float Temperature { get; set; } // 0.0 = deterministic, 1.0 = creative
public int MaxContextTokens { get; set; } // e.g. 200000 for Claude
public int MaxOutputTokens { get; set; } // max tokens in one response
public string? Endpoint { get; set; } // for Azure OpenAI or local models
}
API keys are NEVER stored in plain configuration. The CredentialId field is an integer reference to a securely stored secret in the credential vault. At runtime, ICredentialResolver.GetPasswordAsync(credentialId) retrieves the key.
Memory Configuration
The MemoryConfig on the agent controls which memory types are active and their retention settings:
public class MemoryConfig
{
public bool EpisodicEnabled { get; set; } // default: true
public bool SemanticEnabled { get; set; } // default: true (if vector store configured)
public bool ProceduralEnabled { get; set; } // default: false
public int EpisodicTtlDays { get; set; } // 0 = never expire
public int MaxWorkingMemoryTokens { get; set; } // context window budget
public WorkingMemoryPruningStrategy PruningStrategy { get; set; } // FIFO, Summarize, SlidingWindow
public int SemanticTopK { get; set; } // how many chunks to inject per turn
}
Tool Registry
The ToolRegistry holds all MCP tool definitions that this agent can call. Tools are discovered at agent startup by querying each registered MCP server's GET /tools endpoint:
public class ToolRegistry
{
private readonly Dictionary<string, ToolDefinition> _tools = new();
public void Register(ToolDefinition tool) => _tools[tool.Name] = tool;
public IReadOnlyList<ToolDefinition> GetAll() => _tools.Values.ToList();
public ToolDefinition? Get(string name) =>
_tools.TryGetValue(name, out var t) ? t : null;
}
public class ToolDefinition
{
public string Name { get; set; }
public string Description { get; set; } // LLM reads this to decide when to call
public JsonSchema InputSchema { get; set; } // JSON Schema for tool parameters
public JsonSchema? OutputSchema { get; set; }
public string McpServerUrl { get; set; } // which server handles this tool
}
Persona
The agent persona defines the user-facing identity:
public class AgentPersona
{
public string DisplayName { get; set; } // shown in chat UI
public string AvatarUrl { get; set; }
public string Tone { get; set; } // "formal", "casual", "technical"
public string Language { get; set; } // "en", "es", "fr", etc.
public bool EmojiAllowed { get; set; }
public string DomainExpertise { get; set; } // "HR", "Finance", "IT Support"
}
Goal Definition
Goals are structured (not free-form) and feed into the system prompt via template variables:
public class AgentGoal
{
public string Purpose { get; set; } // "Help employees with HR questions"
public List<string> Capabilities { get; set; } // what the agent can do
public List<string> OutOfScope { get; set; } // what it should decline
public List<string> EscalationCriteria { get; set; } // when to escalate to human
}
// In the system prompt template:
// {{goal.Purpose}}
// Capabilities: {{goal.Capabilities | join ', '}}
// Do not assist with: {{goal.OutOfScope | join ', '}}
Agent Lifecycle in Code
Creating and registering an agent in Octopus:
// Registration at startup (agents loaded from database)
services.AddOctopus(options =>
{
options.UsePlugin<SqlServerStoragePlugin>();
options.UsePlugin<SemanticKernelPlugin>();
});
// Creating an agent via the service layer
var agent = new AgentComposite
{
Name = "HR Assistant",
TenantId = tenantId,
LLMConfig = new LLMConfig
{
Provider = "Anthropic",
Model = "claude-sonnet-4-6",
CredentialId = 42, // stored in credential vault
MaxContextTokens = 200_000,
Temperature = 0.3f
},
Persona = new AgentPersona
{
DisplayName = "Aria",
Tone = "formal",
Language = "en"
}
};
await _agentService.CreateAsync(agent);
Once an AgentComposite is loaded from the database, it is cached in memory. The LLM provider and tool discovery results are cached until the agent configuration is explicitly updated. This avoids the overhead of re-reading config on every conversation turn.