a11oy / web /agent-loop.html
betterwithage's picture
chore(sync): mirror front-door files to Space (hf-sync)
e97df21 verified
Raw
History Blame Contribute Delete
16.2 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>a11oy — Agent Loop · Memory · Skill Library</title>
<!-- 0 runtime CDN: shared engines served locally from /static/shared. -->
<script src="/static/shared/szl_label_engine.js"></script>
<script src="/static/shared/szl_receipt_cosign.js"></script>
<style>
:root{--bg:#0b0e14;--panel:#121826;--ink:#e6edf3;--mut:#8b98a9;--acc:#5bd6ff;
--ok:#3fd37f;--bad:#ff6b6b;--warn:#ffce5b;--line:#1f2937;}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--ink);
font:14px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
header{padding:16px 22px;border-bottom:1px solid var(--line);
display:flex;align-items:center;gap:14px;flex-wrap:wrap}
h1{font-size:17px;margin:0;letter-spacing:.3px}
.sub{color:var(--mut);font-size:12px}
.tabs{display:flex;gap:6px;padding:10px 18px;border-bottom:1px solid var(--line)}
.tab{padding:7px 14px;border:1px solid var(--line);border-radius:8px;
background:var(--panel);cursor:pointer;color:var(--mut)}
.tab.active{color:var(--ink);border-color:var(--acc);box-shadow:0 0 0 1px var(--acc) inset}
main{padding:18px 22px;max-width:1180px}
.view{display:none}.view.active{display:block}
.card{background:var(--panel);border:1px solid var(--line);border-radius:12px;
padding:16px;margin:0 0 16px}
.row{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:10px}
input,textarea,select{background:#0d1320;border:1px solid var(--line);
color:var(--ink);border-radius:8px;padding:8px 10px;font:inherit}
input[type=text]{min-width:340px}
textarea{width:100%;min-height:64px}
button{background:var(--acc);color:#06222c;border:0;border-radius:8px;
padding:8px 14px;font:inherit;font-weight:700;cursor:pointer}
button.ghost{background:#0d1320;color:var(--ink);border:1px solid var(--line);font-weight:500}
.lbl{font-size:11px;font-weight:700;padding:2px 7px;border-radius:6px;
border:1px solid var(--line);margin-left:6px}
.node{display:inline-block;min-width:96px;text-align:center;border-radius:6px;
padding:2px 8px;font-weight:700;font-size:12px}
.THOUGHT{background:#12304a;color:#9fdcff}
.ACTION{background:#3a2a12;color:#ffce8a}
.OBSERVATION{background:#123a24;color:#8ff0bb}
.GENESIS{background:#21262d;color:#9fb2c8}
.ok{color:var(--ok)}.bad{color:var(--bad)}.mut{color:var(--mut)}
.mono{font-family:inherit}.hash{color:var(--mut);font-size:11px;word-break:break-all}
table{width:100%;border-collapse:collapse;font-size:12.5px}
th,td{text-align:left;padding:6px 8px;border-bottom:1px solid var(--line);vertical-align:top}
th{color:var(--mut);font-weight:600}
.bar{height:8px;border-radius:4px;background:#0d1320;overflow:hidden;min-width:90px}
.bar>i{display:block;height:100%;background:var(--acc)}
pre{white-space:pre-wrap;word-break:break-word;margin:6px 0 0;color:var(--mut);font-size:12px}
.pill{font-size:11px;border:1px solid var(--line);border-radius:20px;padding:2px 9px;color:var(--mut)}
code{background:#0d1320;padding:1px 5px;border-radius:5px}
</style>
</head>
<body>
<header>
<h1>a11oy · AGENTIC CORE</h1>
<span id="topbadges"></span>
<span class="sub">ReAct graph · signed receipt boundaries · SqliteSaver resume · Generative-Agents memory · Letta tiering · Voyager skills</span>
</header>
<div class="tabs">
<div class="tab active" data-v="loop">Agent Loop</div>
<div class="tab" data-v="memory">Memory</div>
<div class="tab" data-v="skills">Skill Library</div>
</div>
<main>
<!-- ===================== AGENT LOOP ===================== -->
<section class="view active" id="v-loop">
<div class="card">
<div class="row">
<input id="goal" type="text" placeholder="goal, e.g. implement a function that validates an email address" value="implement a function that validates an email address"/>
<label class="sub">max_steps <input id="maxsteps" type="number" value="4" style="width:64px"/></label>
<label class="sub"><input id="kill" type="checkbox"/> kill mid-run (crash demo)</label>
<button id="btnRun">Run ReAct</button>
<button class="ghost" id="btnResume">Resume last</button>
</div>
<div class="sub">Each node transition (Thought→Action→Observation) is a hash-chained, DSSE-signed receipt. A crash resumes from the last SqliteSaver checkpoint. <span id="runlbl"></span></div>
</div>
<div class="card" id="runMeta" style="display:none"></div>
<div class="card" id="traceCard" style="display:none">
<div class="row" style="justify-content:space-between">
<strong>Execution trace + receipt chain</strong>
<span id="chainState"></span>
</div>
<table id="traceTbl"><thead><tr>
<th>seq</th><th>node</th><th>body</th><th>restraint</th><th>link</th><th>signature</th><th>hash</th>
</tr></thead><tbody></tbody></table>
<div class="sub" style="margin-top:6px">Action nodes that <strong>write code</strong> are gated through <strong>a11oy Restraint</strong> (the Ponytail-derived 6-rung frugality ladder, <a href="https://github.com/DietrichGebert/ponytail" target="_blank" rel="noopener">Ponytail MIT</a> — adopted + governed). The chosen rung + signed restraint receipt are recorded in that node's receipt. <strong>R1 owns the ladder module</strong>; honest <strong>PENDING</strong> until it is live.</div>
</div>
<div class="card" id="cpCard" style="display:none">
<strong>Checkpoints (SqliteSaver-style)</strong>
<table id="cpTbl"><thead><tr><th>step</th><th>prev_hash</th><th>ts</th></tr></thead><tbody></tbody></table>
</div>
</section>
<!-- ===================== MEMORY ===================== -->
<section class="view" id="v-memory">
<div class="card">
<div class="sub">Generative-Agents retrieval: <code>score(m)=α_rec·γ^Δt + α_imp·imp(m) + α_rel·cos(q,m)</code> (arXiv 2304.03442). Embeddings are LOCAL (hashing-trick, 0 CDN) <span class="lbl" style="border-color:var(--warn);color:var(--warn)">HEURISTIC</span></div>
<div class="row" style="margin-top:10px">
<input id="memText" type="text" placeholder="add a memory (observation / fact / lesson)"/>
<button id="btnMemAdd">Add</button>
</div>
<div class="row">
<input id="memQ" type="text" placeholder="retrieve query, e.g. fuel ignition launch"/>
<button id="btnMemSearch">Retrieve (scored)</button>
</div>
</div>
<div class="card" id="memCard" style="display:none">
<strong>Scored retrieval</strong>
<table id="memTbl"><thead><tr>
<th>score</th><th>recency γ^Δt</th><th>imp</th><th>cos(q,m)</th><th>tier</th><th>text</th>
</tr></thead><tbody></tbody></table>
</div>
<div class="card" id="tierCard">
<div class="row" style="justify-content:space-between">
<strong>Letta tiering — working (in-context) + archival (vector)</strong>
<button class="ghost" id="btnTiers">Refresh tiers</button>
</div>
<div id="tierBody" class="sub">working cap = paging boundary; least-recently-used working items page out to archival.</div>
</div>
</section>
<!-- ===================== SKILL LIBRARY ===================== -->
<section class="view" id="v-skills">
<div class="card">
<div class="sub">Voyager skill library (arXiv 2305.16291): a tool-recipe is admitted <em>only</em> after a <strong>verified execution receipt</strong>. Indexed by local embedding.</div>
<div class="row" style="margin-top:10px">
<input id="skName" type="text" placeholder="skill name" style="min-width:180px"/>
<input id="skRecipe" type="text" placeholder="recipe (e.g. calc {a}*{b})" style="min-width:220px"/>
<input id="skRun" type="text" placeholder="run_id of a verified run" style="min-width:180px"/>
<button id="btnAdmit">Admit (receipt-gated)</button>
</div>
<div id="admitMsg" class="sub"></div>
</div>
<div class="card">
<div class="row" style="justify-content:space-between">
<strong>Admitted skills</strong>
<button class="ghost" id="btnSkills">Refresh</button>
</div>
<table id="skTbl"><thead><tr>
<th>name</th><th>recipe</th><th>receipt</th><th>verified</th><th>uses</th>
</tr></thead><tbody></tbody></table>
</div>
</section>
</main>
<script>
const API = "/api/a11oy/v1/agent/react";
const L = (window.SZLLabels && window.SZLLabels.badgeHTML) ? window.SZLLabels.badgeHTML : (k)=>("<span class='lbl'>"+k+"</span>");
let LAST_RUN = localStorage.getItem("a11oy_last_run") || "";
document.getElementById("topbadges").innerHTML = L("EXPERIMENTAL") + " " + L("HEURISTIC");
// tabs
document.querySelectorAll(".tab").forEach(t=>t.onclick=()=>{
document.querySelectorAll(".tab").forEach(x=>x.classList.remove("active"));
document.querySelectorAll(".view").forEach(x=>x.classList.remove("active"));
t.classList.add("active");
document.getElementById("v-"+t.dataset.v).classList.add("active");
});
async function jpost(u,b){const r=await fetch(u,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(b||{})});return {status:r.status,json:await r.json()};}
async function jget(u){const r=await fetch(u);return {status:r.status,json:await r.json()};}
function esc(s){return (s==null?"":String(s)).replace(/[&<>"']/g,function(c){return({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]||c);});}
// ---------- Agent Loop ----------
// Restraint verdict cell for a node body. Only code-writing ACTION nodes carry
// a `restraint` block; everything else shows a dim em-dash (honest).
function restraintCell(body){
const rs = body && body.restraint;
if(!rs) return "<span class='mut'>—</span>";
const live = rs.status==="LIVE";
const badge = live ? L("LIVE") : (L("ROADMAP")+" <span class='lbl' style='border-color:var(--warn);color:var(--warn)'>PENDING</span>");
if(rs.stopped_at_rung!=null){
const saved = (rs.lines_saved_estimate!=null)
? (" · saved≈"+rs.lines_saved_estimate+" LOC ("+esc(rs.lines_saved_label||"MODELED")+")") : "";
const lam = (rs.lambda_advisory!=null) ? (" · Λ "+Number(rs.lambda_advisory).toFixed(3)) : "";
const ceil = rs.restraint_comment ? ("<div class='mut' style='font-size:11px'><code>"+esc(rs.restraint_comment)+"</code></div>") : "";
return "<div><span class='ok'>rung "+rs.stopped_at_rung+"</span> "+esc(rs.rung_key||"")+" "+badge+"</div>"+
"<div class='mut' style='font-size:11px'>"+esc(rs.rung_name||"")+saved+lam+"</div>"+ceil;
}
return "<div>"+badge+"</div><div class='mut' style='font-size:11px'>code gated; restraint not live yet (no rung fabricated)</div>";
}
async function renderTrace(runId){
const {json:tr} = await jget(API+"/trace/"+encodeURIComponent(runId));
if(tr.error){return;}
document.getElementById("traceCard").style.display="block";
document.getElementById("chainState").innerHTML =
(tr.chain_intact?"<span class='ok'>chain intact ✓</span>":"<span class='bad'>chain BROKEN ✗</span>")
+" · depth "+tr.depth;
const tb=document.querySelector("#traceTbl tbody");tb.innerHTML="";
tr.receipts.forEach(r=>{
const sig = r.signature_valid===true?"<span class='ok'>verified ✓</span>"
:(r.signed?"<span class='bad'>FAIL ✗</span>":"<span class='mut'>unsigned</span>");
const link = r.link_ok?"<span class='ok'>✓</span>":"<span class='bad'>✗</span>";
tb.innerHTML += `<tr><td>${r.seq}</td><td><span class="node ${r.node}">${r.node}</span></td>
<td><pre>${esc(JSON.stringify(r.body))}</pre></td><td>${restraintCell(r.body)}</td><td>${link}</td><td>${sig}</td>
<td class="hash">${esc((r.hash||"").slice(0,18))}…</td></tr>`;
});
if(tr.reflection){
document.getElementById("runlbl").innerHTML =
" · reflection (Reflexion): <em>"+esc(tr.reflection.slice(0,90))+"</em>";
}
}
async function renderCps(runId){
const {json:cp}=await jget(API+"/checkpoints/"+encodeURIComponent(runId));
document.getElementById("cpCard").style.display="block";
const tb=document.querySelector("#cpTbl tbody");tb.innerHTML="";
cp.checkpoints.forEach(c=>tb.innerHTML+=`<tr><td>${c.step}</td><td class="hash">${esc((c.prev_hash||"").slice(0,18))}…</td><td class="mut">${esc(c.ts)}</td></tr>`);
}
function showRunMeta(o){
const m=document.getElementById("runMeta");m.style.display="block";
const st = o.status==="completed"?"ok":(o.status==="interrupted"?"bad":"mut");
m.innerHTML = `<strong>run</strong> <code>${esc(o.run_id)}</code> · status <span class="${st}">${esc(o.status)}</span>`
+ (o.answer?` · answer <span class="ok">${esc(o.answer)}</span>`:"")
+ (o.resumed?` · <span class="pill">resumed from step ${o.resumed_from_checkpoint_step}</span>`:"")
+ " " + L(o.label||"EXPERIMENTAL");
}
document.getElementById("btnRun").onclick=async()=>{
const goal=document.getElementById("goal").value;
const max_steps=+document.getElementById("maxsteps").value;
const body={goal,max_steps};
if(document.getElementById("kill").checked) body.kill_after=2;
const {json:o}=await jpost(API+"/run",body);
LAST_RUN=o.run_id;localStorage.setItem("a11oy_last_run",o.run_id);
showRunMeta(o);await renderTrace(o.run_id);await renderCps(o.run_id);
};
document.getElementById("btnResume").onclick=async()=>{
if(!LAST_RUN){alert("run something first");return;}
const {json:o}=await jpost(API+"/resume",{run_id:LAST_RUN});
showRunMeta(o);await renderTrace(LAST_RUN);await renderCps(LAST_RUN);
};
// ---------- Memory ----------
document.getElementById("btnMemAdd").onclick=async()=>{
const text=document.getElementById("memText").value;if(!text)return;
await jpost(API+"/memory/add",{text,tier:"archival",run_id:LAST_RUN||"adhoc"});
document.getElementById("memText").value="";loadTiers();
};
document.getElementById("btnMemSearch").onclick=async()=>{
const query=document.getElementById("memQ").value;if(!query)return;
const {json:o}=await jpost(API+"/memory/search",{query,top_k:8});
document.getElementById("memCard").style.display="block";
const tb=document.querySelector("#memTbl tbody");tb.innerHTML="";
(o.results||[]).forEach(h=>{
const c=h.components;
tb.innerHTML+=`<tr><td><strong>${h.score.toFixed(3)}</strong></td>
<td>${c.recency_gamma_dt.toFixed(3)} <span class="mut">(Δt ${c.delta_t_hours}h)</span></td>
<td>${c.importance.toFixed(2)}</td><td>${c.relevance_cos.toFixed(3)}</td>
<td><span class="pill">${esc(h.tier)}</span></td><td>${esc(h.text)}</td></tr>`;
});
};
async function loadTiers(){
const u = API+"/memory/tiers"+(LAST_RUN?("?run_id="+encodeURIComponent(LAST_RUN)):"");
const {json:o}=await jget(u);
const t=o.tiers||{};
document.getElementById("tierBody").innerHTML =
`working <strong>${t.working||0}</strong> · archival <strong>${t.archival||0}</strong> · cap ${o.working_cap} `
+ L(o.label||"EXPERIMENTAL")
+ `<div class="mut" style="margin-top:6px">${esc(o.design||"")}</div>`;
}
document.getElementById("btnTiers").onclick=loadTiers;
// ---------- Skills ----------
document.getElementById("btnAdmit").onclick=async()=>{
const name=document.getElementById("skName").value;
const recipe=document.getElementById("skRecipe").value;
const run_id=document.getElementById("skRun").value||LAST_RUN;
const {status,json:o}=await jpost(API+"/skills/admit",{name,recipe,run_id});
const el=document.getElementById("admitMsg");
el.innerHTML = (o.admitted?"<span class='ok'>":"<span class='bad'>")+esc(o.reason)+"</span>";
loadSkills();
};
async function loadSkills(){
const {json:o}=await jget(API+"/skills");
const tb=document.querySelector("#skTbl tbody");tb.innerHTML="";
(o.skills||[]).forEach(s=>{
tb.innerHTML+=`<tr><td><strong>${esc(s.name)}</strong></td><td><code>${esc(s.recipe)}</code></td>
<td class="hash">${esc((s.receipt_hash||"").slice(0,16))}…</td>
<td>${s.receipt_verified?"<span class='ok'>✓</span>":"<span class='bad'>✗</span>"}</td>
<td>${s.uses}</td></tr>`;
});
}
document.getElementById("btnSkills").onclick=loadSkills;
// boot
loadTiers();loadSkills();
</script>
</body>
</html>