Procedural Memory
Procedural memory stores how to do things — ordered sequences of steps and tool calls that agents can recall and replay when given a matching task. It enables consistent, repeatable task execution without re-deriving the approach each time.
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:
Task Description Extracted
The user's message is analyzed for a task pattern. e.g., "Onboard the new vendor TechCorp" → task: "onboard vendor"
Procedure Match Search
IProceduralMemoryStore.FindMatchAsync(task) — pattern matching against all approved procedures for this agent.
Procedure Injected into Context
Matched procedure steps are injected into the system prompt as: "To accomplish this task, follow these steps: [1] ... [2] ..."
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." }
}
}