Working memory asks what matters now. KV memory retrieves an exact fact. Vector memory finds relevant knowledge. Episodic memory recalls what happened before.
An operational agent still needs one more kind of memory:
How should this task be performed?
That is procedural memory: approved, reusable knowledge about how to act.
First: What Is Procedural Memory?
A procedure is a versioned playbook with ordered steps, preconditions, completion conditions, and escalation rules.
matching issue
↓
approved active procedure
↓
ordered application-controlled steps
↓
completed or escalated execution
It is different from remembering one past case.
EPISODIC PROCEDURAL
CASE-101 FAILED_PAYMENT_PLAYBOOK v1
what happened? what should happen?
actions actually taken approved ordered steps
case-specific outcome reusable completion rules
Why Not Put Every Playbook in the System Prompt?
A large permanent prompt makes operational knowledge difficult to retrieve, approve, version, test, and audit.
Procedural memory keeps global rules and task-specific rules separate:
system instructions
→ invariants that always apply
procedural memory
→ approved process for one class of task
The application retrieves only the relevant active procedure. The model sees a smaller, explicit policy surface for the current task.
The Stored Procedure Is Structured
The procedure definition is not a paragraph that the model interprets freely:
@dataclass(frozen=True, slots=True)
class SupportProcedure:
id: str
procedure_key: str
version: int
name: str
description: str
category: str
procedure_summary: str
status: ProcedureStatus
steps: tuple[ProcedureStep, ...]
preconditions: tuple[str, ...]
completion_conditions: tuple[str, ...]
escalation_rules: tuple[str, ...]
metadata: dict[str, JSONValue]
Each step has a sequence, action, optional tool, and required flag.
ProcedureStep(
sequence=3,
action="check_gateway",
tool="check_gateway",
required=True,
)
The JSONB steps are the execution source of truth.
Search Uses a Compact Procedure Summary
Embedding raw JSON would mix field names and storage syntax into the semantic representation. Instead, the manager builds readable search text:
Failed Payment Resolution.
Approved procedure for failed checkout payments.
Category: payments.
Use for: payment failed; checkout failed; card charged without order.
Important actions: identify transaction, check transaction,
check gateway, check refund, prevent duplicate refund, notify customer.
Escalate for: inconsistent transaction; gateway unavailable.
The summary finds the playbook. The structured row executes it.
issue embedding → ranked active procedures → exact JSONB steps
Only Active Procedures Are Eligible
A procedure version has one of three states:
DRAFT
ACTIVE
DEPRECATED
The lifecycle is explicit:
create v1 → draft → activate
↓
create v2 → draft → activate v2
↓
v1 becomes deprecated
PostgreSQL enforces at most one active version for each procedure key with a partial unique index.
Activating a new version and deprecating the previous one happen inside a transaction. Only drafts may be deleted; active and deprecated versions remain auditable.
Retrieval Selects One Approved Playbook
search_procedures() embeds the issue and uses pgvector cosine distance. It can apply a category and minimum similarity threshold.
no candidate above threshold
→ normal support triage or escalation
one or more candidates
→ select the highest relevant active procedure
This learning project deliberately executes one procedure. Combining multiple playbooks would require conflict rules, dependency resolution, and a clearer approval model.
Execution Belongs to Application Code
After selection, ProcedureExecutor walks the stored steps in order.
load exact active procedure
↓
start execution record
↓
start step → run allowed action → persist result
↓
repeat
↓
complete or escalate
The model does not choose arbitrary SQL, reorder steps, activate a draft, or invent a tool name. Tool names come from a fixed allowlist.
Runtime State Is Separate From the Definition
The project stores executions independently:
support_procedures
→ immutable approved definition
procedure_executions
→ one run against exact procedure ID and version
procedure_step_executions
→ ordered status and result for each step
Executing a playbook never edits its definition. This separation makes it possible to answer both:
What was the approved process?
What actually happened during this run?
Safety Rules Are Enforced Below the Model
The deterministic support layer enforces several critical constraints:
- only active procedures can execute
check_refundmust precedeissue_refund- refund issuance is idempotent
- tool names come from an allowlist
- unknown or inconsistent transaction state escalates
- customer messages use current tool results
- user text cannot administer procedure versions
These constraints remain in force even if model output is malformed or adversarial.
What Actually Happened?
I ran the real procedural manager, PostgreSQL store, pgvector retrieval, executor, and support tools against the isolated Docker test database. A deterministic test embedding provider made the trace reproducible.
The query selected the active failed-payment playbook and executed all six steps:
selected: FAILED_PAYMENT_PLAYBOOK v1 score=0.7263
execution: completed steps= 6
1. identify_transaction
→ {'transaction_id': 'TXN-DEMO-001', 'identified': True}
2. check_transaction
→ {'state': 'failed', 'amount': '49.00 USD',
'order_created': False}
3. check_gateway
→ {'result': 'timeout', 'reference': 'gw-demo-1'}
4. check_refund
→ {'state': 'auto_refund_triggered',
'eta_business_days': 5}
5. prevent_duplicate_refund
→ {'safe_to_issue': False,
'refund_state': 'auto_refund_triggered'}
6. notify_customer
→ {'notified': True,
'message': 'Transaction TXN-DEMO-001: refund status is
auto_refund_triggered. Expected processing time: 5 business days.'}
outcome: procedure completion conditions satisfied
procedure unchanged: True
The executor did not issue another refund because current state showed that an automatic refund already existed.
The final equality check confirms that runtime execution left the approved definition unchanged.
Failure Becomes Escalation
The same executor has explicit terminal behavior for unsafe states.
missing transaction ID
→ escalate
unknown transaction state
→ escalate
gateway unavailable
→ escalate
tool failure
→ mark step failed and escalate
An escalation is a recorded outcome, not an exception that disappears from operational history.
The Model Writes the Grounded Answer Last
After deterministic execution, the Responses API receives:
selected procedure identity and version
completion and escalation conditions
available tool names
terminal execution state
current observations
current user issue
The model produces the natural-language response. It cannot claim that a step occurred unless the persisted execution marks it complete, and it must draw current facts from tool observations.
This places language generation after policy selection and execution rather than using language generation as the enforcement mechanism.
Procedures Should Not Learn Themselves
Successful episodes can reveal a better process, but they should not modify live policy automatically.
many episodes
↓
offline analysis
↓
human review
↓
new draft procedure version
↓
approval and activation
This preserves accountability. A production behavior change has an author, version, approval transition, and audit trail.
What the Tests Prove
I ran the full suite against the repository's local PostgreSQL/pgvector test database:
38 passed in 0.38s
The tests cover persistence, JSONB ordering, semantic retrieval, filters and thresholds, active-only selection, version transitions, uniqueness, rollback, execution state, step state, escalation, immutable definitions, duplicate-refund safety, response grounding, and the user/admin boundary.
Embeddings and support tools are deterministic in tests, and no OpenAI key is required.
The Five Memory Types Together
The projects now form a useful map:
Working memory
→ What is happening now?
KV memory
→ What exact fact do I know?
Vector memory
→ What knowledge is semantically relevant?
Episodic memory
→ What happened in a similar case?
Procedural memory
→ How should this task be performed?
They can coexist in one agent, but each needs its own write policy, retrieval contract, lifecycle, and trust boundary.
What I Took Away
Procedural memory turns operational knowledge into a controlled software artifact.
retrieve by meaning
approve and version explicitly
execute from structure
persist every run
generate language from observed results
The procedure guides action, application code enforces safety, and the model communicates the result. Keeping those responsibilities separate makes the system easier to test and audit.
Source Code
The complete project includes PostgreSQL migrations, pgvector retrieval, versioned procedures, deterministic execution, support tools, Responses API answer generation, Docker setup, a Rich terminal UI, and integration tests.
GITHUB / SOURCE CODEExplore the procedural-memory implementationsriraman-10 / applied-ai-concepts / memory / 05-procedural-memory