Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion site/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<div class="links">
<a href="#how">Why</a>
<a href="/demo.html">Live demo</a>
<a href="/proofs.html">Proofs</a>
<a href="https://github.com/buttercode101/agentscope">GitHub →</a>
</div>
</nav>
Expand All @@ -41,7 +42,8 @@
<p class="tag">open source · stdlib-only · MIT</p>
<h1>Your agents worked all night. <em>Know if anything went wrong.</em></h1>
<p class="hero-sub">AgentScope turns scheduled agent activity into one quiet morning screen. No orchestration. No trace maze. Just the failures, drift and missing work that deserve your attention.</p>
<div class="cta-row"><a class="btn" href="/demo.html">Open the live demo ↗</a><a class="btn btn-ghost" href="https://github.com/buttercode101/agentscope">Get the source</a></div>
<div class="cta-row"><a class="btn" href="/demo.html">Open the live demo ↗</a><a class="btn btn-ghost" href="https://github.com/buttercode101/agentscope">Get the source</a>
<a class="btn btn-ghost" href="/proofs.html">Inspect proof snapshots</a></div>
<p class="microline">Python 3.11+ · zero runtime dependencies · self-hosted · MIT</p>
</div>
<figure class="stage" aria-label="AgentScope product preview">
Expand Down
26 changes: 26 additions & 0 deletions site/proofs-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"base_url": "",
"generated": "2026-10-21",
"status": "published-historical-snapshots",
"note": "Historical Habitat proof exports. Browser checks validate structure and content hash only; unsigned bundles do not establish publisher identity or external truth.",
"projects": {
"Greenlight-K53": [
{
"run_id": "greenlight_k53-9dc5a00-0",
"proof_url": "/proofs/Greenlight-K53/greenlight_k53-9dc5a00-0/proof.json",
"claim": "Deployed commit 9dc5a00: fix: refresh K53 database cache when online",
"content_sha256": "faaa6dfb4453224294a8b8a3e192acc2b1d96f41d08f260919c19fcf732b45b2",
"source": "historical Habitat export"
}
],
"Diketo": [
{
"run_id": "diketo-30feea5-0",
"proof_url": "/proofs/Diketo/diketo-30feea5-0/proof.json",
"claim": "Deployed commit 30feea5: Add files via upload",
"content_sha256": "10c0bd36495d6cc4fb93da55c5d33e315170cc23a7f6bb476c3875bd6094f6ed",
"source": "historical Habitat export"
}
]
}
}
8 changes: 8 additions & 0 deletions site/proofs.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="theme-color" content="#f7f7f5"><title>Evidence proofs — AgentScope</title><meta name="description" content="Inspect historical Habitat proof exports and independently check their content hashes."><style>
:root{--bg:#f7f7f5;--ink:#0b0d10;--mut:#626a73;--line:#dedfdf;--card:#fff;--blue:#315cff;--green:#137a43;--amber:#8a5a12;--red:#a32929}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:15px/1.6 system-ui,-apple-system,"Segoe UI",sans-serif}.wrap{width:min(920px,calc(100% - 32px));margin:auto}nav{height:72px;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between}nav a{color:inherit;text-decoration:none;font-weight:700}.mono{font:11px ui-monospace,monospace;letter-spacing:.08em;text-transform:uppercase;color:var(--blue)}h1{font:800 clamp(34px,6vw,56px)/1.05 ui-monospace,monospace;letter-spacing:-.06em;margin:54px 0 14px}.lede{color:var(--mut);max-width:700px}.notice{border:1px solid #e3d4b4;background:#fffaf0;padding:14px 16px;border-radius:9px;color:#604c2a;font-size:13px;margin:26px 0}.list{display:grid;gap:12px;margin:28px 0}.proof{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:20px}.proof h2{font:800 17px ui-monospace,monospace;margin:0 0 5px}.meta{color:var(--mut);font-size:12px}.claim{margin:14px 0}.checks{display:flex;flex-wrap:wrap;gap:7px}.check{border:1px solid var(--line);border-radius:6px;padding:4px 8px;font:10px ui-monospace,monospace}.ok{color:var(--green);border-color:#b8ddc6}.warn{color:var(--amber);border-color:#e5d3ad}.bad{color:var(--red);border-color:#e6bcbc}.proof a{display:inline-block;margin-top:14px;color:var(--blue);font:700 12px ui-monospace,monospace}.empty{padding:20px;border:1px dashed var(--line);color:var(--mut)}footer{border-top:1px solid var(--line);padding:24px 0 40px;color:var(--mut);font-size:12px}a:focus-visible{outline:3px solid var(--blue);outline-offset:3px}@media(max-width:500px){.proof{padding:15px}.checks{gap:5px}}</style></head><body><div class="wrap"><nav><a href="/">agentscope /</a><a href="/demo.html">Demo ↗</a></nav><main><p class="mono">Habitat · portable evidence</p><h1>Proof snapshots.</h1><p class="lede">Historical Habitat proof exports copied into AgentScope. Each bundle is checked in your browser for its embedded SHA-256 content digest and claim/action consistency.</p><div class="notice"><strong>Important:</strong> a matching content hash detects changes to the exported bundle, but does not authenticate who published it. These are historical records, not live status. An unsigned proof does not independently establish that the claimed work happened in the outside world.</div><div id="proof-list" class="list" aria-live="polite"><div class="empty">Loading proof index…</div></div></main><footer>AgentScope · Evidence is labelled by what can actually be checked. <a href="/">Back to AgentScope</a></footer></div>
<script>
function canonical(v){if(Array.isArray(v))return "["+v.map(canonical).join(",")+"]";if(v&&typeof v==="object")return "{"+Object.keys(v).sort().map(k=>JSON.stringify(k)+":"+canonical(v[k])).join(",")+"}";return JSON.stringify(v)}
async function verify(b){const e=[];for(const k of ["proof_version","generated_at","claim","ledger","content_sha256"])if(!(k in b))e.push("Missing "+k);if(b.proof_version!=="1")e.push("Unsupported proof version");if(!b.claim||typeof b.claim!=="object")e.push("Claim object missing");if(!b.ledger||!Array.isArray(b.ledger.actions))e.push("Ledger actions missing");if(b.ledger&&!["intact","failed"].includes(b.ledger.integrity))e.push("Invalid ledger integrity");if(e.length)return{ok:false,errors:e};const input={...b};delete input.generated_at;delete input.content_sha256;delete input.signature;const digest=[...new Uint8Array(await crypto.subtle.digest("SHA-256",new TextEncoder().encode(canonical(input))))].map(x=>x.toString(16).padStart(2,"0")).join("");if(digest!==b.content_sha256)e.push("Content hash mismatch");const c=b.claim,ev=c.evidence||{};if(c.status==="verified"){if(!ev.action_id)e.push("Verified claim lacks evidence action ID");const action=b.ledger.actions.find(x=>x.id===ev.action_id);if(!action)e.push("Evidence action absent from ledger");else if(action.status!==c.expected_status||action.action!==c.action)e.push("Evidence action does not match claim")}return{ok:!e.length,errors:e,digest,signature:b.signature?"present":"absent"}}
function el(t,c,s){const n=document.createElement(t);if(c)n.className=c;if(s!==undefined)n.textContent=s;return n}
async function main(){const host=document.getElementById("proof-list");try{const r=await fetch("/proofs-manifest.json",{cache:"no-store"});if(!r.ok)throw Error("Proof manifest unavailable ("+r.status+")");const m=await r.json();host.replaceChildren();for(const [project,items]of Object.entries(m.projects||{}))for(const item of items){const card=el("article","proof");card.append(el("h2","",project+" / "+item.run_id),el("div","meta",item.claim+" · "+item.source));const response=await fetch(item.proof_url,{cache:"no-store"});if(!response.ok){card.append(el("p","check bad","Could not load proof ("+response.status+")"));host.append(card);continue}const b=await response.json(),v=await verify(b),checks=el("div","checks");card.append(el("p","claim",b.claim?.claim||"Claim text unavailable"));checks.append(el("span","check "+(v.ok?"ok":"bad"),v.ok?"CONTENT HASH MATCH":"INTEGRITY CHECK FAILED"),el("span","check "+(b.ledger.integrity==="intact"?"ok":"bad"),"LEDGER: "+b.ledger.integrity.toUpperCase()),el("span","check warn","SIGNATURE: "+(v.signature||"absent").toUpperCase()),el("span","check","CLAIM: "+String(b.claim.status||"unknown").toUpperCase()));card.append(checks);if(v.errors.length)card.append(el("p","meta",v.errors.join("; ")));card.append(el("p","meta","Generated "+b.generated_at+" · SHA-256 "+(v.digest||"unavailable")));const a=el("a","","Open raw proof JSON ↗");a.href=item.proof_url;a.target="_blank";a.rel="noopener noreferrer";card.append(a);host.append(card)}if(!host.children.length)host.append(el("div","empty","No proof snapshots published."))}catch(e){host.replaceChildren(el("div","empty",e.message))}}main();
</script></body></html>
46 changes: 46 additions & 0 deletions site/proofs/Diketo/diketo-30feea5-0/proof.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"proof_version": "1",
"generated_at": "2026-09-24T22:25:10.769186+00:00",
"claim": {
"id": "claim-diketo-30feea5-0",
"habitat_id": "hermes-local",
"job_id": "hermes-job",
"claim": "Deployed commit 30feea5: Add files via upload",
"action": "run_job",
"expected_status": "ok",
"created_at": "2026-09-24T22:25:10.759897+00:00",
"verified_at": "2026-09-24T22:25:10.767097+00:00",
"status": "verified",
"evidence": {
"source": "habitat_trusted_ledger",
"action_id": "act-diketo-30feea5-0",
"action": "run_job",
"status": "ok",
"timestamp": "2026-09-24T22:25:10.759897+00:00",
"run_id": "diketo-30feea5-0",
"details": {
"source": "hermes_hmac_adapter"
}
},
"run_id": "diketo-30feea5-0"
},
"ledger": {
"integrity": "intact",
"actions": [
{
"id": "act-diketo-30feea5-0",
"habitat_id": "hermes-local",
"job_id": "hermes-job",
"timestamp": "2026-09-24T22:25:10.759897+00:00",
"actor": "agent",
"action": "run_job",
"status": "ok",
"details": {
"source": "hermes_hmac_adapter"
},
"run_id": "diketo-30feea5-0"
}
]
},
"content_sha256": "10c0bd36495d6cc4fb93da55c5d33e315170cc23a7f6bb476c3875bd6094f6ed"
}
46 changes: 46 additions & 0 deletions site/proofs/Greenlight-K53/greenlight_k53-9dc5a00-0/proof.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"proof_version": "1",
"generated_at": "2026-09-24T22:25:06.994797+00:00",
"claim": {
"id": "claim-greenlight_k53-9dc5a00-0",
"habitat_id": "hermes-local",
"job_id": "hermes-job",
"claim": "Deployed commit 9dc5a00: fix: refresh K53 database cache when online",
"action": "run_job",
"expected_status": "ok",
"created_at": "2026-09-24T22:25:06.985061+00:00",
"verified_at": "2026-09-24T22:25:06.992541+00:00",
"status": "verified",
"evidence": {
"source": "habitat_trusted_ledger",
"action_id": "act-greenlight_k53-9dc5a00-0",
"action": "run_job",
"status": "ok",
"timestamp": "2026-09-24T22:25:06.985061+00:00",
"run_id": "greenlight_k53-9dc5a00-0",
"details": {
"source": "hermes_hmac_adapter"
}
},
"run_id": "greenlight_k53-9dc5a00-0"
},
"ledger": {
"integrity": "intact",
"actions": [
{
"id": "act-greenlight_k53-9dc5a00-0",
"habitat_id": "hermes-local",
"job_id": "hermes-job",
"timestamp": "2026-09-24T22:25:06.985061+00:00",
"actor": "agent",
"action": "run_job",
"status": "ok",
"details": {
"source": "hermes_hmac_adapter"
},
"run_id": "greenlight_k53-9dc5a00-0"
}
]
},
"content_sha256": "faaa6dfb4453224294a8b8a3e192acc2b1d96f41d08f260919c19fcf732b45b2"
}
24 changes: 24 additions & 0 deletions tests/test_public_proofs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import json
from pathlib import Path

from habitat.proof_verify import verify_file


ROOT = Path(__file__).resolve().parents[1]


def test_published_habitat_proof_snapshots_verify_and_match_source_exports():
manifest = json.loads((ROOT / "site" / "proofs-manifest.json").read_text(encoding="utf-8"))
for project, entries in manifest["projects"].items():
for entry in entries:
relative = entry["proof_url"].lstrip("/")
published = ROOT / "site" / relative
source = ROOT / "web" / "public" / relative
assert published.is_file(), f"missing published proof: {published}"
assert source.is_file(), f"missing source proof: {source}"
assert published.read_bytes() == source.read_bytes()
result = verify_file(published)
assert result["valid"], result["errors"]
assert result["assurance"]["publisher_trust"] == "not-assessed"
assert result["assurance"]["external_truth"] == "not-established"
assert result["assurance"]["signature"] == "absent"
Loading