Multi-step execution creates decisions, artifact versions, test records and operations with unknown outcomes. Separate information purpose from persistence, build checkpoints and conditional updates, and use recovery tests to see whether stored state is sufficient to continue.
Separate Lifecycle and Usage
Model parameters carry capabilities and knowledge acquired during training; application state carries the inputs, decisions, files, and execution results for this specific task. The current context is the material actually accessible during this inference, which is usually only a subset of the saved state. You cannot infer that "since an interface requires explicit history transmission, all model knowledge comes from history," nor can you treat "summaries" as inherently ephemeral data.
Information Form
Primary Use
Retained Across Restarts?
Conversation and tool history
Reproduce interactions, resume protocols
Depends on whether it falls into persistent backend
Compressed summaries
Reduce context, quick recovery
Can be saved; not determined by summary format
Task checkpoints
Restore execution position and to-dos
Requires explicit persistence configuration
Long-term memory
Reuse preferences or conventions across tasks
Requires validity period, source, and access scope
Authoritative business data
Facts like inventory, orders, permissions
Managed by corresponding business systems
"Short-term memory" sometimes refers to state belonging only to a single thread, not necessarily RAM. For example, LangGraph distinguishes between thread checkpoints and cross-thread stores, but the in-memory implementation itself does not survive process restarts; you need to choose an appropriate persistent backend. LangGraph Persistence
If you resend the growing history every round, the input per round increases with the history, and the cumulative input for the entire multi-round task may grow even faster. Assuming h tokens are added per round, the count of resent new history for n rounds is h×n(n+1)/2; prefix caching can change computation or cost, but it cannot remove this history's use of context space. See Context Engineering for window management.
What Checkpoints Need to Save
Checkpoints should allow the recovery program to answer: What is the current goal? Which actions are definitely completed? Which action results are unknown? What needs to be checked before the next step? Saving only the final natural language summary often loses these boundaries.
Suppose an Agent is fixing an importer, has submitted a patch and run unit tests, but hasn't done a restart recovery test. You could use the following application customization record:
{"task_id":"importer-fix-42","revision":7,"state":"running","goal":"Fix duplicate imports, keep existing interfaces","artifacts":[{"path":"src/importer.py","revision":"patch-3"}],"verified":[{"check":"unit","artifact_revision":"patch-3","report":"artifacts/unit-patch-3.txt"}],"pending":["restart-recovery-test"],"unknown_operations":[],"next_action":"Verify patch-3 against test report, then execute recovery test"}
revision identifies the version of this state record; artifact_revision identifies the version of the artifact the test targeted; the two are not the same counter. Actual systems can use commit IDs, content hashes, or their own version identifiers, but they must be able to locate the corresponding content.
Upon recovery, first read the checkpoint, then verify if the artifacts exist and match the version. If the file has been modified to patch-4, old test results only prove patch-3 and cannot directly continue marking "all verification complete." Finally, handle unknown_operations: when a remote write operation has been issued but the response is lost, query its true status first; do not blindly replay. See Agent Loop for side-effect recovery boundaries.
Checkpoints and external operations are usually not in the same transaction. Recording completion before execution creates a window where the record leads the fact; executing before recording creates a window where the fact leads the record. You need to handle this with mechanisms like business idempotency keys, result queries, or shared transactions, rather than relying on "auto-saving chat logs" to eliminate duplicate executions.
From Candidate Facts to Valid Memories
Save Information That Changes Future Actions
Information suitable for long-term storage is stable and reusable, such as "project release artifacts must include a compatibility report." Thousands of lines of complete output from a single tool are usually better saved as raw evidence, while memory retains only the conclusion and location. Both can coexist, avoiding repeated loading of large history segments every time.
A memory entry should at least be able to explain the content, scope, and source:
Field
Example
Problem Solved
Content
Project P's default report language is Chinese
How to change behavior next time
Scope
User U, Project P
Does not apply to other users or projects
Source
User explicitly requested this time, message ID
Distinguish user decisions from model speculation
Status
active, superseded, unverified
Does not treat old decisions as current constraints
Time or Version
Update time, applicable project version
Determine if review is needed
Original Location
Requirement doc or record location
Can re-read in case of dispute
"One fact per file" facilitates manual management but is not a universal requirement. When data volume is large, concurrency is needed, or complex filtering is required, database entries may be more suitable; the key is that entries can be independently located, updated, and revoked, and sources and scopes are preserved during retrieval.
Memory Writes Can Also Fail
The model saying "users always prefer very short answers" might just be a preference inferred from a single task. Its speculative nature should be preserved; speculation should not be upgraded to permanent user authorization. When new requirements conflict with old memories, first judge the scope and time; explicit new decisions should replace old entries, rather than letting two conflicting records coexist.
Repeated summarization gradually loses qualifiers. For example, the original text is "test environments allow skipping approval," but the secondary summary becomes "allow skipping approval," changing the permission meaning. For such constraints, preserve the original location and qualified scope; upon retrieving the memory, still verify against the current task and authorization.
Claude's memory tool is a client-side execution interface: the model requests file operations, the application maps logical memory paths to its own storage, and returns the result. Declaring a tool does not mean persistence is implemented, nor does it mean the service automatically manages tenants and permissions. Memory tool
How do two workers overwrite state?
Lost updates need no storage corruption: two individually plausible writes suffice. CAS includes the version used for the decision; after rejection recompute, rather than relabeling the old write with a new version.
Preparing the visual
How do two workers overwrite state?
CAS compares expected versions and rejects stale overwrites; rereading still requires recomputing the update.
{"id":"ai-state-cas","title":"How do two workers overwrite state?","summary":"CAS compares expected versions and rejects stale overwrites; rereading still requires recomputing the update.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><label class=\"check\"><input type=\"checkbox\" id=\"cas\" checked><span data-i18n=\"cas\"></span></label><div class=\"actions\"><button id=\"commitA\" data-i18n=\"commitA\"></button><button id=\"commitB\" data-i18n=\"commitB\"></button><button id=\"refresh\" data-i18n=\"refresh\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><section class=\"panel\"><h3 data-i18n=\"state\"></h3><div id=\"state\"></div></section><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"explain\"></p></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control output{color:var(--accent);text-align:right;font-variant-numeric:tabular-nums;min-width:4em}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1;min-width:0;overflow-wrap:anywhere;font-size:13px;min-height:44px}.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin:16px 0}.actions button{min-width:0;min-height:44px;font-size:13px;overflow-wrap:anywhere}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0;overflow-wrap:anywhere}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;margin:18px 0}.metrics>div{border-left:2px solid var(--accent);padding:0 6px 0 10px;min-width:0}.metrics span{display:block;min-height:3.6em;color:var(--muted);font-size:12px;overflow-wrap:anywhere}.metrics strong{font-size:21px;display:block;min-height:2em;font-weight:550;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.check{display:flex;align-items:center;gap:9px;font-size:13px;margin:14px 0}.check input{width:18px;height:18px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:14px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:44px}.panel{padding:14px;border:1px solid var(--rule);border-radius:9px;margin:16px 0;min-width:0}.panel>header{font-size:13px;font-weight:600;margin-bottom:12px;color:var(--muted)}.panel>div{overflow-wrap:anywhere}.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:8px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:56px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:6px 3px;font-size:12px;overflow-wrap:anywhere}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.table{display:grid;gap:5px;font-size:12px}.tr{display:grid;gap:5px}.tr>div{background:var(--surface);border-radius:4px;padding:9px 6px;min-width:0;min-height:5em;overflow-wrap:anywhere;display:flex;align-items:center}.tr>.gold{background:var(--second-soft)}.tr>.on{background:var(--accent-soft)}.formula,.source{font:13px/1.8 ui-monospace,monospace;white-space:pre-wrap;overflow-wrap:anywhere;margin:16px 0}.formula{border-top:1px solid var(--rule);padding-top:12px;min-height:7.2em}.source{min-height:8em;background:var(--surface);padding:12px;border-radius:8px}.intervals{margin:16px 0}.interval{position:relative;height:38px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/38px ui-monospace,monospace;padding-left:7px}.bars{display:grid;gap:10px;margin:16px 0}.bar-row{display:grid;grid-template-columns:44px minmax(0,1fr) 62px;gap:8px;align-items:center;font:12px ui-monospace,monospace}.bar-track{height:24px;background:var(--surface);border-radius:4px;overflow:hidden}.bar-track i{display:block;height:100%;background:var(--accent);transition:width .2s}.bar-track i.gold{background:var(--second)}.bar-track i.empty{opacity:.2}.bar-row output{text-align:right}.diagram{display:block;width:100%;height:auto;margin:18px 0}.diagram text{font:13px ui-monospace,monospace;fill:var(--ink)}.node{fill:var(--surface);stroke:var(--rule)}.node.on{fill:var(--accent-soft);stroke:var(--accent)}.node.gold{fill:var(--second-soft);stroke:var(--second)}.edge{stroke:var(--rule);stroke-width:2;fill:none}.edge.on{stroke:var(--accent)}.matrix{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;margin:16px 0}.matrix button{min-width:0;min-height:44px;font:13px ui-monospace,monospace}.matrix button[aria-pressed=true]{background:var(--accent-soft);border-color:var(--accent)}.matrix button.masked{opacity:.35}.legend{font-size:12px;color:var(--muted);min-height:3.6em;overflow-wrap:anywhere}.numeric{font-variant-numeric:tabular-nums}.scope p{line-break:strict}@media(max-width:480px){.presets button,.actions button{font-size:12px}.panel{padding:12px}.metrics{gap:10px}.metrics strong{font-size:19px}.bar-row{grid-template-columns:34px minmax(0,1fr) 58px;gap:6px}}@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n\n.panel>h3{font-size:13px;font-weight:600;margin:0 0 12px;color:var(--muted)}\n\n.bar-row{grid-template-columns:44px minmax(0,1fr) 82px}.bar-row output{white-space:nowrap}@media(max-width:480px){.bar-row{grid-template-columns:34px minmax(0,1fr) 76px}}\n\n","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';row.style.gridTemplateColumns='repeat('+r.length+',minmax(0,1fr))';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}function plot(id,series,xr,yr,opts={}){const e=$('#'+id),X=x=>42+(x-xr[0])/(xr[1]-xr[0])*298,Y=y=>175-(y-yr[0])/(yr[1]-yr[0])*148;let out='<defs><clipPath id=\"clip-'+id+'\"><rect x=\"42\" y=\"27\" width=\"298\" height=\"148\"/></clipPath></defs>';for(let i=0;i<3;i++){let x=xr[0]+i*(xr[1]-xr[0])/2,y=yr[0]+i*(yr[1]-yr[0])/2;out+='<path class=\"gridline\" d=\"M '+X(x)+' 27V175M42 '+Y(y)+'H340\"/><text x=\"'+X(x)+'\" y=\"194\" text-anchor=\"middle\">'+esc(opts.xfmt?opts.xfmt(x):Number(x.toFixed(2)))+'</text><text x=\"36\" y=\"'+(Y(y)+4)+'\" text-anchor=\"end\">'+esc(opts.yfmt?opts.yfmt(y):Number(y.toFixed(2)))+'</text>';}out+='<g clip-path=\"url(#clip-'+id+')\">';for(const s of series){out+='<path fill=\"none\" stroke=\"'+(s.color||'var(--accent)')+'\" stroke-width=\"2.5\" '+(s.dash?'stroke-dasharray=\"5 4\"':'')+' d=\"'+s.pts.map((p,i)=>(i?'L':'M')+X(p[0]).toFixed(2)+','+Y(p[1]).toFixed(2)).join(' ')+'\"/>';}for(const p of opts.points||[])out+='<circle cx=\"'+X(p[0])+'\" cy=\"'+Y(p[1])+'\" r=\"4\" fill=\"var(--second)\" stroke=\"var(--paper)\" stroke-width=\"1.5\"/>';if(opts.cursor!==undefined)out+='<path d=\"M'+X(opts.cursor)+' 27V175\" stroke=\"var(--muted)\" stroke-dasharray=\"3 3\"/>';e.innerHTML=out+'</g>';e.dataset.curves=JSON.stringify(series.map(s=>s.pts));}const curve=(fn,lo,hi,n=120)=>Array.from({length:n+1},(_,i)=>{const x=lo+(hi-lo)*i/n;return[x,fn(x)]});function inputs(draw){document.querySelectorAll('input,select').forEach(e=>{e.addEventListener('input',draw);e.addEventListener('change',draw)});draw()}const n=id=>+$('#'+id).value;const show=(id,v,d=3)=>set(id,Number(v.toFixed(d)));function bars(id,values,max=1){$('#'+id).className='bars';$('#'+id).innerHTML=values.map(v=>'<div class=\"bar-row\"><span>'+esc(v.label)+'</span><div class=\"bar-track\"><i class=\"'+(v.cls||'')+'\" style=\"width:'+Math.max(0,Math.min(100,v.value/max*100))+'%\"></i></div><output>'+esc(v.text??(v.value*100).toFixed(1)+'%')+'</output></div>').join('')}function softmax(z,T=1){let max=Math.max(...z),w=z.map(x=>Math.exp((x-max)/T)),sum=w.reduce((a,b)=>a+b,0);return w.map(x=>x/sum)}let v=7,s=[],bv=7,bs=[],result='none';function draw(){table('state',[[t('shared'),'v'+v+' '+fmt(s)],['A','v7 → {x}'],['B','v'+bv+' → '+fmt(new Set([...bs,'y']))],[t('result'),t(result)]]);set('explanation',t('explain'));document.body.dataset.state=JSON.stringify(s);document.body.dataset.version=v;document.body.dataset.result=result;}function commit(expected,next){if($('#cas').checked&&expected!==v)result='reject';else{s=[...new Set(next)].sort();v++;result='ok'}draw()}$('#commitA').onclick=()=>commit(7,['x']);$('#commitB').onclick=()=>commit(bv,[...bs,'y']);$('#refresh').onclick=()=>{bv=v;bs=[...s];draw()};$('#reset').onclick=()=>{v=7;s=[];bv=7;bs=[];result='none';draw()};draw();","audio":false,"strings":{"cas":"Check version at commit","commitA":"Commit A’s x","commitB":"Commit B’s y","explain":"Commit A then B: without checking, y overwrites x; with checking, B is rejected. B must reread and recompute to preserve both x and y.","heading":"How do two workers overwrite state?","intro":"A and B both read v7; commit changes with or without version checks.","none":"No commit","ok":"Accepted","refresh":"B rereads and recomputes","reject":"Stale version rejected","reset":"Reset","result":"Last commit","scope":"Shared state is a task set: A adds x, B adds y. Recompute unions the current set with the task; not general automatic conflict resolution.","scopeLabel":"Model scope and assumptions","shared":"Current shared state","state":"Snapshots and shared state"}}
Versioned Updates and Merging
Why "Read Then Overwrite" Loses Decisions
A and B both read version v7. A adds "report includes source," and B adds "record time range." If both overwrite the same file entirely, B's later write might erase A's modifications. Guaranteeing atomic visibility for a single write does not solve the problem of modifying based on an old version.
One approach is conditional updates: carry the read version upon submission, and the storage only writes and increments if the version still matches. After a conflict occurs, re-read and determine if the two changes can be merged; if they involve mutually exclusive decisions, resolve them according to business rules, rather than mechanically concatenating them.
The following demonstrates conditional updates using a temporary SQLite database, and verifies the saved results by reopening after closing the connection. The stale versions of the two editors are simulated via sequential submissions; this is not a concurrency stress or power-failure test.
importsqlite3importtempfilefrompathlibimportPathwithtempfile.TemporaryDirectory()asdirectory:path=Path(directory)/"memory.sqlite"db=sqlite3.connect(path)db.execute("""CREATETABLEmemory (
owner TEXT, key TEXT, version INTEGERNOT NULL, value TEXT,
PRIMARY KEY(owner, key))""")db.execute("INSERT INTO memory VALUES (?, ?, ?, ?)",("user-1","report-rule",7,"Record time range"))db.commit()defupdate(expected,value):withdb:cursor=db.execute("""UPDATE memory
SET value=?, version=version+1WHERE owner=? AND key=? AND version=?""",(value,"user-1","report-rule",expected))returncursor.rowcount==1assertupdate(7,"Record time range, and include source")assertnotupdate(7,"Stale version overwrite")db.close()reopened=sqlite3.connect(path)row=reopened.execute("SELECT version, value FROM memory WHERE owner=? AND key=?",("user-1","report-rule")).fetchone()assertrow==(8,"Record time range, and include source")reopened.close()print(row)
SQLite's UPDATE only modifies records satisfying the WHERE clause; if no records match, it is not considered a SQL error; therefore, you must check the number of affected rows. SQLite UPDATE In the example, owner is fixed on the trusted application side; real services must derive accessible scopes from authenticated identities and cannot let model parameters specify someone else's owner.
Conditional updates prevent silent overwrites but do not guarantee that the merged content is true. Stricter systems also need version history, auditing, and deletion markers; if restoring backups or rebuilding indexes, you must avoid already revoked entries being re-invoked by old copies.
Recovery Testing and Isolation Boundaries
Test Persistence, Retrieval, and Correct Usage Separately
Check
Method
What Failure Means
Save successful
Read after closing backend connection or restarting process
Data might only be in RAM, or commit incomplete
Retrieval correct
Retrieve using relevant and irrelevant tasks respectively
Issues with index, scope, or sorting
Usage correct
Provide old memory and new explicit requirements
Outdated records might override current needs
Concurrency safe
Two writers update based on the same version
Might silently overwrite
Task recovery
Interrupt before tool, after tool, after result save
Might cause duplicate side effects or false completion reports
Deletion effective
Retrieve again after deletion and check derived indexes
Old vectors, caches, or copies still active
Successful saving and reading are just the first hurdle. Acceptance testing for "can continue after closing and reopening" should also check if it repeatedly explores negated solutions, retains user constraints, and if to-dos match actual artifacts. Engineering experience with long-running Agents also emphasizes progress logging, environment checks, and verifiable incremental results. Effective harnesses for long-running agents
File Directories Cannot Replace Full Permission Models
Self-managed file memories need to constrain root directories, resolve paths, handle symbolic links, and manage write races. Checking resolve() before open() has a time gap; in directories subject to concurrent modification, a TOCTOU (Time-of-Check to Time-of-Use) race might occur; you cannot describe a single string or path check as a complete file isolation solution. Execution identity, directory permissions, and appropriate restricted file operation mechanisms need to jointly bear the boundary.
Memories are not suitable for storing key bodies; credentials are provided by dedicated credential management components; document links and resource handles in entries must also have permissions checked upon use. Filter by user, project, and authorization scope before retrieval; do not fetch the entire database and then ask the model "don't look at other users' data." These requirements work in conjunction with the tool boundaries in Security and Protection.
Restored state: is evidence still valid?
Recovery must reestablish trustworthy facts. Unchanged filenames and summaries do not mean tests cover current content; binding reports to artifact versions or hashes reveals stale evidence.
Preparing the visual
Restored state: is evidence still valid?
Checkpoints retain references and known facts; old tests do not validate new artifacts, and unknown effects require reconciliation.
{"id":"ai-checkpoint-evidence","title":"Restored state: is evidence still valid?","summary":"Checkpoints retain references and known facts; old tests do not validate new artifacts, and unknown effects require reconciliation.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"actions\"><button id=\"edit\" data-i18n=\"edit\"></button><button id=\"test\" data-i18n=\"test\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><label class=\"check\"><input type=\"checkbox\" id=\"unknown\"><span data-i18n=\"unknown\"></span></label><section class=\"panel\"><h3 data-i18n=\"state\"></h3><div id=\"state\"></div></section><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"explain\"></p></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control output{color:var(--accent);text-align:right;font-variant-numeric:tabular-nums;min-width:4em}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1;min-width:0;overflow-wrap:anywhere;font-size:13px;min-height:44px}.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin:16px 0}.actions button{min-width:0;min-height:44px;font-size:13px;overflow-wrap:anywhere}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0;overflow-wrap:anywhere}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;margin:18px 0}.metrics>div{border-left:2px solid var(--accent);padding:0 6px 0 10px;min-width:0}.metrics span{display:block;min-height:3.6em;color:var(--muted);font-size:12px;overflow-wrap:anywhere}.metrics strong{font-size:21px;display:block;min-height:2em;font-weight:550;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.check{display:flex;align-items:center;gap:9px;font-size:13px;margin:14px 0}.check input{width:18px;height:18px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:14px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:44px}.panel{padding:14px;border:1px solid var(--rule);border-radius:9px;margin:16px 0;min-width:0}.panel>header{font-size:13px;font-weight:600;margin-bottom:12px;color:var(--muted)}.panel>div{overflow-wrap:anywhere}.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:8px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:56px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:6px 3px;font-size:12px;overflow-wrap:anywhere}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.table{display:grid;gap:5px;font-size:12px}.tr{display:grid;gap:5px}.tr>div{background:var(--surface);border-radius:4px;padding:9px 6px;min-width:0;min-height:5em;overflow-wrap:anywhere;display:flex;align-items:center}.tr>.gold{background:var(--second-soft)}.tr>.on{background:var(--accent-soft)}.formula,.source{font:13px/1.8 ui-monospace,monospace;white-space:pre-wrap;overflow-wrap:anywhere;margin:16px 0}.formula{border-top:1px solid var(--rule);padding-top:12px;min-height:7.2em}.source{min-height:8em;background:var(--surface);padding:12px;border-radius:8px}.intervals{margin:16px 0}.interval{position:relative;height:38px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/38px ui-monospace,monospace;padding-left:7px}.bars{display:grid;gap:10px;margin:16px 0}.bar-row{display:grid;grid-template-columns:44px minmax(0,1fr) 62px;gap:8px;align-items:center;font:12px ui-monospace,monospace}.bar-track{height:24px;background:var(--surface);border-radius:4px;overflow:hidden}.bar-track i{display:block;height:100%;background:var(--accent);transition:width .2s}.bar-track i.gold{background:var(--second)}.bar-track i.empty{opacity:.2}.bar-row output{text-align:right}.diagram{display:block;width:100%;height:auto;margin:18px 0}.diagram text{font:13px ui-monospace,monospace;fill:var(--ink)}.node{fill:var(--surface);stroke:var(--rule)}.node.on{fill:var(--accent-soft);stroke:var(--accent)}.node.gold{fill:var(--second-soft);stroke:var(--second)}.edge{stroke:var(--rule);stroke-width:2;fill:none}.edge.on{stroke:var(--accent)}.matrix{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;margin:16px 0}.matrix button{min-width:0;min-height:44px;font:13px ui-monospace,monospace}.matrix button[aria-pressed=true]{background:var(--accent-soft);border-color:var(--accent)}.matrix button.masked{opacity:.35}.legend{font-size:12px;color:var(--muted);min-height:3.6em;overflow-wrap:anywhere}.numeric{font-variant-numeric:tabular-nums}.scope p{line-break:strict}@media(max-width:480px){.presets button,.actions button{font-size:12px}.panel{padding:12px}.metrics{gap:10px}.metrics strong{font-size:19px}.bar-row{grid-template-columns:34px minmax(0,1fr) 58px;gap:6px}}@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n\n.panel>h3{font-size:13px;font-weight:600;margin:0 0 12px;color:var(--muted)}\n\n.bar-row{grid-template-columns:44px minmax(0,1fr) 82px}.bar-row output{white-space:nowrap}@media(max-width:480px){.bar-row{grid-template-columns:34px minmax(0,1fr) 76px}}\n\n","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';row.style.gridTemplateColumns='repeat('+r.length+',minmax(0,1fr))';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}function plot(id,series,xr,yr,opts={}){const e=$('#'+id),X=x=>42+(x-xr[0])/(xr[1]-xr[0])*298,Y=y=>175-(y-yr[0])/(yr[1]-yr[0])*148;let out='<defs><clipPath id=\"clip-'+id+'\"><rect x=\"42\" y=\"27\" width=\"298\" height=\"148\"/></clipPath></defs>';for(let i=0;i<3;i++){let x=xr[0]+i*(xr[1]-xr[0])/2,y=yr[0]+i*(yr[1]-yr[0])/2;out+='<path class=\"gridline\" d=\"M '+X(x)+' 27V175M42 '+Y(y)+'H340\"/><text x=\"'+X(x)+'\" y=\"194\" text-anchor=\"middle\">'+esc(opts.xfmt?opts.xfmt(x):Number(x.toFixed(2)))+'</text><text x=\"36\" y=\"'+(Y(y)+4)+'\" text-anchor=\"end\">'+esc(opts.yfmt?opts.yfmt(y):Number(y.toFixed(2)))+'</text>';}out+='<g clip-path=\"url(#clip-'+id+')\">';for(const s of series){out+='<path fill=\"none\" stroke=\"'+(s.color||'var(--accent)')+'\" stroke-width=\"2.5\" '+(s.dash?'stroke-dasharray=\"5 4\"':'')+' d=\"'+s.pts.map((p,i)=>(i?'L':'M')+X(p[0]).toFixed(2)+','+Y(p[1]).toFixed(2)).join(' ')+'\"/>';}for(const p of opts.points||[])out+='<circle cx=\"'+X(p[0])+'\" cy=\"'+Y(p[1])+'\" r=\"4\" fill=\"var(--second)\" stroke=\"var(--paper)\" stroke-width=\"1.5\"/>';if(opts.cursor!==undefined)out+='<path d=\"M'+X(opts.cursor)+' 27V175\" stroke=\"var(--muted)\" stroke-dasharray=\"3 3\"/>';e.innerHTML=out+'</g>';e.dataset.curves=JSON.stringify(series.map(s=>s.pts));}const curve=(fn,lo,hi,n=120)=>Array.from({length:n+1},(_,i)=>{const x=lo+(hi-lo)*i/n;return[x,fn(x)]});function inputs(draw){document.querySelectorAll('input,select').forEach(e=>{e.addEventListener('input',draw);e.addEventListener('change',draw)});draw()}const n=id=>+$('#'+id).value;const show=(id,v,d=3)=>set(id,Number(v.toFixed(d)));function bars(id,values,max=1){$('#'+id).className='bars';$('#'+id).innerHTML=values.map(v=>'<div class=\"bar-row\"><span>'+esc(v.label)+'</span><div class=\"bar-track\"><i class=\"'+(v.cls||'')+'\" style=\"width:'+Math.max(0,Math.min(100,v.value/max*100))+'%\"></i></div><output>'+esc(v.text??(v.value*100).toFixed(1)+'%')+'</output></div>').join('')}function softmax(z,T=1){let max=Math.max(...z),w=z.map(x=>Math.exp((x-max)/T)),sum=w.reduce((a,b)=>a+b,0);return w.map(x=>x/sum)}let v=3,r=3;function draw(){let ok=v===r&&!$('#unknown').checked;table('state',[[t('artifact'),'v'+v],[t('report'),'v'+r],[t('unknown'),$('#unknown').checked?'?':'—'],[t('accepted'),ok?'✓':'×']]);set('explanation',t('explain'));document.body.dataset.accepted=ok;}$('#edit').onclick=()=>{v=4;draw()};$('#test').onclick=()=>{r=v;draw()};$('#reset').onclick=()=>{v=3;r=3;$('#unknown').checked=false;draw()};inputs(draw);","audio":false,"strings":{"accepted":"Recovery acceptance passes","artifact":"Artifact version","edit":"Edit artifact to v4","explain":"Successful serialization only makes state readable. Acceptance also needs matching versions, trustworthy evidence and reconciled operations; this model shows version and unknown-status checks.","heading":"Restored state: is evidence still valid?","intro":"Change artifact version or introduce an unknown write; inspect recovery acceptance.","report":"Version tested by report","reset":"Reset","scope":"Artifact v3 has a v3 report; one edit advances to v4. The test button models a successful local check, not a real test run.","scopeLabel":"Model scope and assumptions","state":"Recovery acceptance","test":"Revalidate current artifact","unknown":"A write outcome is unknown"}}