Procedural Memory: Evolving Instructions
Store versioned, approved rules for how the agent should behave—not just facts it knows or examples it has seen.
The third memory type changes policy
Semantic memory says, “The user avoids meetings before 10:00.” Episodic memory says, “A similar invitation was handled by offering two times and waiting.” Procedural memory says, “For external attendees, always propose times and obtain approval before creating an event.”
Procedures are instructions, rubrics, routing rules, and tool-use constraints. They can improve behavior across many requests, but they also carry the greatest blast radius. A false fact can distort one topic; a poisoned procedure can distort every future action.
Procedural memory must therefore behave more like configuration or code than casual recall.
PROCEDURAL MEMORY
current approved policy
│
┌───────────────┴───────────────┐
▼ ▼
EPISODE RETRIEVAL RESPONSE AGENT
│ │
▼ ▼
TRIAGE MEMORY + TOOLSStore immutable prompt versions
Do not overwrite a shared prompt string in place. Store immutable versions with status, provenance, evaluation evidence, approver, and timestamps. One version is active for a defined scope; earlier versions remain available for audit and rollback.
class ProcedureVersion(BaseModel):
procedure_id: str
version: int
scope: Literal["triage", "response", "memory_policy"]
instructions: str
status: Literal["draft", "approved", "active", "retired"]
proposed_by: str
approved_by: str | None
evaluation_run_id: str | None
created_at: str
The active record might say that newsletters without direct questions are ignored, security notices are surfaced, and external meeting invitations require confirmation. A revision becomes a draft with a new version number; it does not inherit approval simply because most text is unchanged.
Load procedures before decision nodes
A small loader resolves the active version for the authenticated tenant and application. The graph stores both the instructions and version identifier so traces and outcomes can be tied to the exact procedure used.
def load_procedures(state: EmailState, config):
tenant_id = config["configurable"]["tenant_id"]
triage = procedure_store.get_active(tenant_id, scope="triage")
response = procedure_store.get_active(tenant_id, scope="response")
return {
"triage_instructions": triage.instructions,
"triage_procedure_version": triage.version,
"response_instructions": response.instructions,
"response_procedure_version": response.version,
}
The triage prompt combines current approved instructions, reviewed episodic examples, and the current email. Their precedence is explicit: system safety policy first, approved tenant procedure second, past examples as evidence third, untrusted email content last.
Keep instruction generation outside activation
An optimizer can examine corrections and propose a better procedure. It should not activate its own proposal. Generation, evaluation, approval, and rollout are separate transitions.
def propose_procedure_update(current, reviewed_failures):
candidate = optimizer.invoke({
"current_instructions": current.instructions,
"reviewed_failures": reviewed_failures,
"constraint": "Preserve system safety policy and tool authorization.",
})
return procedure_store.create_draft(
scope=current.scope,
instructions=candidate.instructions,
supersedes=current.version,
)
This workflow treats a prompt as a release artifact. A proposal can fail retrieval tests, route regressions, unsafe-action cases, or cost budgets before any user sees it.
observed failures
│
▼
PROPOSE DRAFT ─▶ OFFLINE EVAL ─▶ HUMAN APPROVAL ─▶ CANARY ─▶ ACTIVE
│ │ │ │
└──── reject ◀─┴─────────────────┴──────────────┘Evaluate behavior, not prompt wording
Reviewing a diff is necessary but insufficient. Run a versioned evaluation dataset through the current and candidate procedures. Compare structured route accuracy, critical-class recall, exact tool selection, argument validity, denied-action rate, latency, token use, and cost.
A candidate might be clearer English yet cause more important mail to be ignored. Another might improve overall accuracy while weakening a rare security case. Release gates should reflect consequence, not only averages.
Store the dataset version, model configuration, retrieval corpus version, and result with the procedure. Otherwise a later team cannot reproduce why version 12 was approved.
Roll out and roll back like code
Activate a new procedure for a small cohort or traffic percentage. Trace its version on every decision. Compare online correction and escalation rates with the previous version. If thresholds fail, flip the active pointer back; immutable versions make rollback immediate.
Avoid mixing a prompt rollout with a model migration or embedding change. Changing one major behavioral variable at a time makes causality visible. If several changes must move together, treat the combination as one versioned release bundle and evaluate it as such.
The complete memory hierarchy
The agent now has three forms of continuity:
| Memory type | Question answered | Example | Typical change path |
|---|---|---|---|
| Semantic | What is true or preferred? | No meetings before 10:00 | Extract, verify, store or supersede |
| Episodic | What happened in a similar case? | User corrected this sender’s FYI to ignore | Observe outcome, review, retrieve as example |
| Procedural | How should the agent behave? | Ask before scheduling external attendees | Propose, evaluate, approve, canary, activate |
The categories overlap in language, so policy must classify by function. A sentence that constrains future behavior globally deserves procedural safeguards even if it sounds like a preference.
Key takeaways
- Procedural memory stores rules and instructions that shape many future decisions.
- Prompt versions should be immutable, scoped, evaluated, approved, and reversible.
- An optimizer may propose a procedure but must not activate its own output.
- Retrieved email content can never grant itself authority to become policy.
- Evaluate and roll out procedures as behavioral release artifacts.
Checklist
- [ ] I can distinguish semantic, episodic, and procedural memory by function.
- [ ] I can explain why prompt versions should be immutable.
- [ ] I can draw the draft → evaluation → approval → canary → active workflow.
- [ ] I can name the authority required for a procedural change.
- [ ] I can choose behavioral metrics that would trigger rollback.