Portal Community

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
}
Credential Pattern

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);
Agent Caching

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.