Portal Community

What is a Procedure?

A procedure is a named, stored sequence of steps that an agent has learned (or been taught) for accomplishing a specific type of task. Unlike semantic memory (facts) or episodic memory (conversations), procedural memory captures the how-to — the exact sequence of actions including tool calls, conditional checks, and parameter patterns.

Admin-Defined Procedures

Platform administrators write procedures explicitly in the agents-app Skill Library. These are immediately available to the agent after saving, without any approval step.

Agent-Learned Procedures

When an agent successfully completes a multi-step task, Octopus can auto-capture the tool call sequence as a candidate procedure. These require human review and approval before activation.

Procedure Entity

public class Procedure
{
    public Guid Id { get; init; }
    public string Name { get; set; }              // "vendor_onboarding"
    public string Description { get; set; }       // human-readable purpose
    public string TriggerPattern { get; set; }    // pattern for matching: "onboard * vendor"
    public List<ProcedureStep> Steps { get; set; }
    public Guid AgentId { get; set; }             // owner agent (or null for shared)
    public string TenantId { get; init; }
    public bool IsApproved { get; set; }           // must be true to be active
    public bool IsShared { get; set; }             // available to all agents in tenant
    public DateTimeOffset CreatedAt { get; init; }
    public string CreatedBy { get; set; }          // "admin" or "agent" (auto-captured)
}

public class ProcedureStep
{
    public int Order { get; set; }
    public string Instruction { get; set; }        // what to do in this step
    public string? ToolName { get; set; }          // MCP tool to call (optional)
    public string? ToolArgumentTemplate { get; set; } // JSON with {{parameter}} placeholders
    public string? Condition { get; set; }          // only execute if condition is true
    public bool IsOptional { get; set; }
}

IProceduralMemoryStore Interface

public interface IProceduralMemoryStore
{
    Task StoreAsync(Procedure procedure, CancellationToken ct = default);

    Task<Procedure?> RecallByNameAsync(
        string name, Guid agentId, string tenantId, CancellationToken ct = default);

    Task<Procedure?> FindMatchAsync(
        string taskDescription, Guid agentId, string tenantId,
        CancellationToken ct = default);

    Task<IReadOnlyList<Procedure>> ListAsync(
        Guid agentId, string tenantId, CancellationToken ct = default);

    Task ApproveAsync(Guid procedureId, string tenantId, CancellationToken ct = default);
    Task DeleteAsync(Guid procedureId, string tenantId, CancellationToken ct = default);
}

Procedure Matching at Runtime

Before each LLM call, the MemoryOrchestrator checks if the user's task matches a known procedure:

1

Task Description Extracted

The user's message is analyzed for a task pattern. e.g., "Onboard the new vendor TechCorp" → task: "onboard vendor"

2

Procedure Match Search

IProceduralMemoryStore.FindMatchAsync(task) — pattern matching against all approved procedures for this agent.

3

Procedure Injected into Context

Matched procedure steps are injected into the system prompt as: "To accomplish this task, follow these steps: [1] ... [2] ..."

4

Agent Follows Procedure

The LLM executes the procedure steps in order, calling the specified tools and substituting parameters from the user's request.

Example Procedure — Vendor Onboarding

// Admin-defined vendor onboarding procedure
new Procedure
{
    Name = "vendor_onboarding",
    Description = "Onboard a new vendor into the procurement system",
    TriggerPattern = "onboard * vendor|add new vendor|register vendor",
    Steps = new()
    {
        new ProcedureStep { Order=1, Instruction="Collect vendor name, contact, and contract reference from the user." },
        new ProcedureStep { Order=2, Instruction="Look up the vendor in the existing system.",
            ToolName = "vendor_lookup",
            ToolArgumentTemplate = "{\"name\": \"{{vendor_name}}\"}" },
        new ProcedureStep { Order=3, Instruction="If vendor exists, report to user and stop.",
            Condition = "vendor_lookup.result != null", IsOptional = true },
        new ProcedureStep { Order=4, Instruction="Create the new vendor record.",
            ToolName = "vendor_create",
            ToolArgumentTemplate = "{\"name\":\"{{vendor_name}}\",\"contact\":\"{{contact}}\",\"contract\":\"{{contract_ref}}\"}" },
        new ProcedureStep { Order=5, Instruction="Confirm creation to the user with the new vendor ID." }
    }
}