Releases: NotASithLord/peerd
Release list
peerd-preview-v0.2.7
Added
- Z.ai GLM as a BYOK provider. Use GLM-5.2 (and the rest of the GLM
lineup) directly from z.ai, not routed through OpenRouter. Z.ai's API is
OpenAI-compatible, so the adapter is a thin sibling of the OpenRouter one:
same wire format, different base URL and key shape. Shows up in
Settings under Providers. fetch_urlreturns clean markdown by default. HTML is converted with
vendored Readability and Turndown, running in the offscreen document,
instead of handing back raw page source. Non-article pages, Firefox
(no extraction client there), or an extraction error all fall back to
today's raw behavior. An oversized body no longer silently loses its
tail: it spills to a local cache and the model sees a head and tail
window with a note on how to page through the rest with the new
read_web_cachetool.read_pagegainsmode:'content'. It grabs the rendered DOM after
JavaScript has run and routes it through the same extraction and paging
pipelinefetch_urluses, so a tab-driven task gets the same clean
markdown a fetched page does. The default snapshot mode (interactable
elements for clicking and typing) is unchanged.- An experimental code surface for the web actor (default off, opt-in
under Settings → Behavior). Instead of one tool call per action, the
actor can write Playwright-style JS against a sealedpage.goto/
click/fill/snapshotbridge, with the same gated dispatch,
denylist, and audit as the normal tool-call path underneath. Measured
against the tool-call path on the Online-Mind2Web benchmark it did not
win (26.7% vs 20.7%, more steps, more step-cap failures), so tool calls
stay the default; the code surface ships anyway because a real
benchmark harness for a browser-extension agent came out of building it.
Fixed
- A "New chat" mid-task could leave an abandoned actor still running.
Resetting a session now stops its live turn and cascades the stop to
every actor it had spawned, the same way the Stop button already does. - A spent Z.ai account retried instead of failing over. Z.ai reports
out-of-credit as an HTTP 429 with its own error code, which the retry
classifier didn't recognize, so a drained account burned three retries
before giving up instead of switching providers. - GLM-4.6 was priced about three times too high, which could trip a
spend limit early or inflate the cost display for no reason. Corrected
to its published rate. - An OpenAI streaming error could be blamed on OpenRouter in the
error text, since the shared stream parser wasn't told which provider
it was reading for. - A few research-preset tool manifests were missing
read_web_cache
even though they allowed the tools that produce a spillable body, so a
paged-out fetch or page read left the model unable to read its own
overflow, burning turns on a call the manifest then refused. read_page's content mode could truncate silently. A render past
its size cap now says so explicitly instead of reportingtruncated: falseon a body that was actually cut.- The cache-eviction message for a paged-out read pointed at the wrong
fix. It told the model to re-fetch the URL, which for a rendered
page throws away the post-JavaScript DOM it already had; the hint is
now specific to how the content was originally captured.
Install (preview): Firefox: click the .xpi on this page. Chrome: drag
the .crx into chrome://extensions with Developer mode on. Most users
want the store packages (see the README). The auto-update feeds are
attached here and served at peerd.ai/updates/.
peerd-preview-v0.2.6
Fixed
- A web actor could be wrongly refused a read of its own tab. The
dispatcher gate that pins a web actor to the one tab it owns compared an
explicittabIdargument againstactorInstanceId. That field is the
fixed literal'web', the actor's stablemessage_actoraddress, not a
tab id (only an API actor's instanceId is still a real identifier, its
origin). So the comparison could never match, and any DOM tool call that
named its own tab explicitly (read_page,click, and so on) was refused
with a confusing "pinned to tab web" error, even though it was the actor's
own tab. The gate now compares against the actor's actually-owned tab
instead. This was a false-positive refusal, not a security gap: the
independent execute-time resolver already only ever targeted the owned
tab or failed closed. - The search shortcut in the web actor's own prompt pointed at a URL that
always redirects. The prompt told the actor to search with
fetch_url https://duckduckgo.com/html/?q=..., but that path 302s to
html.duckduckgo.com, andfetch_urldoes not follow redirects (by
design: seedocs/security/THREAT-MODEL.mdINV-7). Every search burned a
turn on a guaranteed redirect error before the actor retried correctly.
The prompt now names the right host (html.duckduckgo.com/html/?q=...)
and says why the bare host fails, so search works on the first try. - A oneShot delegation whose CODE crashed now gets its recovery turn.
The oneShot contract always said "an errored round falls through to the
normal loop", but the clean-round test only saw tool-LEVEL errors, and a
notebook eval whose code threw (a CompileError, a bad import) returns
ok:true with the[ERROR]text as its content. So the crash
short-circuited straight back to the orchestrator as the raw reply and
the actor never debugged its own sandbox (field transcript: a notebook
actor bounced the same CompileError back twice).js_notebooknow marks
such resultsevalErrorand the one-shot latch disarms on it, exactly
like a tool failure: the actor recovers and iterates, as promised. A
headless CI test also now pinspeerd:wasi+demoModule()as
importable and runnable from ascriptjob (a field session reported
the import unreachable; current source proves green, so a stale install
is the likely culprit, reload the extension).
Added
peerd:wasiships a self-test module.demoModule()(exported next
torunWasi) returns a tiny (187-byte) known-good wasm32-wasi hello
module, embedded in the extension, so the agent can smoke-test
runWasi(demoModule())inside the sealed worker with no network and no
toolchain, instead of hunting the web for a working binary (a live
session burned itself on exactly that hunt). The blob is hand-assembled,
regenerable fromtests/notebook-tab/wasi-test-module.ts, and the bun
suite pins the embedded bytes against that builder so blob and source
cannot drift. Taught in thescript/js_createlore and the code-mode
prompt.
Fixed
resp.bytes()works in sandboxed code now. The sealed-realm fetch
bridge (Notebook / script / a2a runs) listedbyteson its response but
as a raw data property, soresp.bytes()(the platformResponse.bytes()
shape every model reaches for) threw "not a function". It's a method
returningPromise<Uint8Array>now, matching the platform. Found in the
field: an agent burned several turns rediscoveringarrayBuffer()while
smoke-testingrunWasi.- The web actor no longer talks itself out of rendering. Field
transcript: asked for live sports schedules, the web actor tried fetches,
declared itself "fetch-only", claimed it lacked an open-tab tool, and
bounced the task back to the user, while the render path was fully wired
(navigatelazily opens + adopts its tab in the 0-tab state). The
machinery was right; the words were wrong. Three model-facing fixes:
navigate's description now states it OPENS the tab when the actor owns
none (it read "navigate the target tab", implying one must exist); the web
actor's lore states it can ALWAYS render and must never report itself
fetch-only; the shared actor rules forbid addressing the user ("would you
like me to…" has no one to answer it, do the work or report what
blocked). And the orchestrator'smessage_actorteaching now says: never
narrate unobserved actor progress, and re-send with the capability
restated when an actor wrongly claims its kind can't do something.
Changed
- "Subagent" is now "actor" everywhere. The heap-split already made a
subagent an ephemeral actor on the same substrate as the bound (sandbox /
web / dweb) actors; the vocabulary now matches. Model-facing: the
spawn_subagenttool isactor_create,subagent_cancelis
actor_cancel,subagent_tasksisactor_tasks(pairs with
sandbox_create: a sandbox always has a dedicated bound actor;
actor_createalone makes an ephemeral one). Internally the
peerd-runtime/subagent/module ispeerd-runtime/actor/, and the
spawned-child concept keeps a distinct name where it must not collide
with bound actors: session kind'subagent'is now'spawned', and
the child transcript stream isturn/spawned-*. Breaking for stored
sessions from earlier builds (0.x posture: no migration shims): old
kind:'subagent'records and skills/evals namingspawn_subagent
need the new names.
Install (preview): Firefox: click the .xpi on this page. Chrome: drag
the .crx into chrome://extensions with Developer mode on. Most users
want the store packages (see the README). The auto-update feeds are
attached here and served at peerd.ai/updates/.
peerd-preview-v0.2.5
Security
- Three residual risks from the threat model narrowed before wider
exposure (R4/R5/R6, each documented honestly in
docs/security/THREAT-MODEL.md):- The audit log is tamper-evident now. Every entry extends a SHA-256
hash chain, and a head record pins the newest link. Rewritten, deleted,
inserted, or truncated entries fail verification. The debug bundle runs
the verification and stamps the result into its provenance. This is
evidence, not proof: in-origin code execution can still recompute the
chain. That boundary is stated, not hidden. - Session confirm grants are origin-bound. "Yes for this session" now
means this tool ON this origin. Approvingclickon one site no longer
silently covers every site the chat visits. This generalizes the host
scoping web writes already had. - Transfer import is gated. Imported provider endpoints must be https
(or local loopback) and are named in the summary the user approves.
Imported hooks land DISABLED and untrusted until re-enabled per hook in
Settings. A memory import states its prompt-injection consequence in the
apply notices.
- The audit log is tamper-evident now. Every entry extends a SHA-256
Added
- Standing peer conversations on the mesh (preview only). The dweb
actor's agent-to-agent surface was single-shot: one ask, one reply, the
thread forgotten. An inbound peer message only ever reported to YOU,
never back to the peer. Now a conversation is a THREAD.mesh.converse(did, message)opens one and returns aconvId. A later peer message on that
thread wakes the dweb actor WITH the prior turns as context, and the actor's
answer goes BACK to the peer. The reply-to-a-peer edge is the owner-chosen
gate: per-conversation reply consent (approve once per thread, revoke by
blocking the peer).mesh.say(convId, message)continues a thread from code.
The convId threads through the wire envelope. A convId is a bearer token, so
only its owning did may extend it. The thread store is capped and TTL-evicted
(a peer can't grow SW memory).dweb_blockcloses every thread with that
peer. Proven over real WebRTC by the two-peer harness (converse → the peer's
reply threads the convId back → say continues it).
Added
- OpenAI provider adapter. Direct BYOK access to OpenAI's own API
(api.openai.com), distinct from reaching OpenAI models through the
OpenRouter gateway. A user with an OpenAI key and no OpenRouter account
now gets first-class access, billed to their OpenAI account. The wire
format is the reference OpenAI/chat/completions, so it reuses the same
request/response formatters as the OpenRouter adapter (retry set, hard-limit
fast-fail, streaming). The key attaches at fetch-header time and never
enters the request body. It shows up in Settings → Providers with the current
GPT-5.x flagships seeded in the picker. The manifest already covered the
host via<all_urls>+ thehttps:CSP, so nothing new is requested. - The debug surface: serious observability without a vendor. peerd's
chain of events was already recorded (audit log, lineage, delegation
traces) but trapped across surfaces. Now it comes OUT, locally, on the
user's say-so. Three pieces:- Debug bundle export: a chip-sized
debugbutton in the chat mode
row saves one JSON file per session. It includes the full transcript
INCLUDING every descendant actor/subagent session (the delegation tree,
walked by parent links), the audit slice for that set, cost, a settings
snapshot (keys can't appear, they live only in the vault and attach at
fetch-header time), live context snapshots, a classified failure index,
and a provenance block that says plainly what may be missing (pruned
audit, evicted snapshots). The same data exports as an OpenTelemetry
trace (OTLP/JSON, delegation = span parentage, gen_ai semconv
attributes) for any OTel viewer the user already runs. It is converted in
the panel from the same payload, with no second route, no wire, and no
vendor. - Failure-class chips: every failed tool card and failed turn now
carries its classified failure neighborhood (policy / auth / limits /
provider / timeout / aborted / environment / agent / internal) as a
small chip next to the raw error, so triage starts at "whose fault,
roughly" instead of string-parsing. The same classifier annotates the
bundle and stamps OTel span status. - The context inspector (dev mode): "what did the model actually
see?". The service worker keeps a small in-memory ring of shaped
request snapshots per session (system prompt clipped, messages capped,
binary payloads stripped with a visible sentinel), captured at the two
seams that together cover every model call: the orchestrator's turn
driver and the actor/subagent relay route. A modal lists each call
(who, model, sizes, content) and is honest about the ring's lifetime:
it empties with the service worker, and says so.
- Debug bundle export: a chip-sized
- The orchestrator delegates from code:
scriptgrows anactorsclient.
The same bet that gave the web actor and the mesh their code surfaces now
reaches the orchestrator itself. Inside thescripttool (the renamed
js_run, the generalized name models actually reach for), code can
await actors.ask(to, goal)to delegate and get the reply back as a value,
actors.send(to, goal)to hand off without waiting, andactors.list()
for the roster. Fan-out and plumbing move into one script: ask several
actors at once, feed one's output into the next as a variable. Intermediate
bytes never transit the orchestrator's context at all, which is both a token
win and a deepening of the isolation thesis.
Nothing new is trusted: every delegation runs the full message_actor gate
chain per call (sender gate, rate caps, duplicate-intent, the oneShot
sandbox rule, audit), the worker can never spoof whose behalf it acts on
(owner identity rides trusted job params), and a script that delegated has
its output fenced (actor replies are untrusted bytes).
Observability is the contract, not an afterthought: every run returns a
[DELEGATIONS] trace (op, target, outcome, timing, with failed-op detail
fenced) that survives script errors, timeouts, and Stop. The side panel
streams a live per-delegation feed on the script card while it runs. Each
op lands in the audit log tagged via:script. Stop actually unwinds the
whole fan (pending asks abort, their actor turns die, the worker is
terminated). Proven end to end by a live e2e state: one script, a real
web-actor round trip, the reply resolving into the running code.
Install (preview): Firefox: click the .xpi on this page. Chrome: drag
the .crx into chrome://extensions with Developer mode on. Most users
want the store packages (see the README). The auto-update feeds are
attached here and served at peerd.ai/updates/.
peerd-preview-v0.2.4
Changed
oneShotdelegation is sandbox-only now.message_actor's oneShot mode
(skip the actor's summary turn, hand the raw result straight back) is
honored only for the agent's own engine sandboxes (webvm/notebook/app) and
refused loudly for every other target. The summary turn is what
incidentally compresses untrusted content, so a web/API/dweb reply always
comes back summarized. The orchestrator prompt now actually teaches the
shortcut ("runpytest" → oneShot:true) instead of leaving it buried in
schema fine print, where models (small local ones especially) never
found it.
Install (preview): Firefox: click the .xpi on this page. Chrome: drag
the .crx into chrome://extensions with Developer mode on. Most users
want the store packages (see the README). The auto-update feeds are
attached here and served at peerd.ai/updates/.
peerd-preview-v0.2.3
Added
- Agent-to-agent over the mesh (A2A): the dweb actor talks to other
agents by writing code (preview only). The same bet as the web actor
applies: models write a short script more fluently than they fire one
gated action per turn. The dweb actor drives peer conversations through a new
a2a_runtool: it writes JS against ameshclient (mesh.peers(),
mesh.card(did),mesh.ask(did, msg),mesh.send(...),
mesh.publishCard(...),mesh.inbox()) and returns the outcome.ask
sends a request-tagged direct message and awaits the peer's one reply; an
Agent Card advertises what your agent can do and is discoverable by other
peers. The data model rhymes with Google's A2A (Agent Card, message shape)
so future interop is a thin adapter, but the transport is the mesh, the
address is a did:key, and the reply stream is the fenced inbound wake, not
A2A's HTTP+SSE. The code runs in the same sealed, keyless worker as
js_runwith one added capability, the mesh bridge, and nothing else: an
a2a run gets no egress and cannot spawn subagents. First contact to a peer
(and advertising your own card) needs your explicit ok, remembered per did
and revocable by blocking the peer; peer replies and cards are always
fenced as untrusted. It's a dweb-actor tool only: the orchestrator never
holds it, and the store build prunes the whole surface. Hardened after an
adversarial re-review: the Agent Card size/field caps are enforced on both
the publish and fetch paths, first-contact "Allow once" is a genuine
one-shot (only "Allow for session" persists, and blocking a peer revokes
it), and the ask/reply round-trip is now covered by a live two-peer
WebRTC test.
Changed
- The orchestrator's tool surface got a hard slim: 27 → 18 always-on.
Three moves, one rule: the main agent bootstraps and delegates, and every
instance byte stays behind an actor heap.- The engine file READS are actor-only now.
js_read_file,
app_read_file, andapp_list_fileshad stayed on the orchestrator as
fenced "cheap reads". But an instance file is not reliably
agent-authored (notebook/app code fetches and persists web data), so even
a fenced read handed untrusted bytes to the orchestrator's context. The
convenience broke the isolation premise. Reads now ride the instance's
actor like every other op. - One
sandbox_create({ kind })replacesvm_create/js_create/
app_create. Same bootstrap, one tool: the webvm/notebook/app taxonomy
is laid out side-by-side in a single description where the model actually
picks, instead of repeated across three. The per-kind create behavior is
unchanged (background tab, go-there card, chat's current, id returned),
and the durable-handle harvest still records which kind an id is (the
result stampskind; the compaction/trim extractors read it). - The instance-gating ("progressive disclosure") machinery is deleted.
Every op it deferred is actor-only now, so it had nothing left to gate.
Its "create one first" refusal for a premature call was the wrong
message anyway; the honest answer is "that's the actor's tool".
inspect_provider_config,inspect_storage,inspect_session_access,
inspect_denylist, andinspect_audit_logwere five near-identical
read-only tools; they collapse into a singleinspect({ kind })(kinds:
provider_config/storage/session_access/denylist/
audit_log), exactly the wayactor_listfolded the per-kind list tools
into one. Behavior per facet is unchanged (same outputs, same audit-log
subagent-error redaction), but the main agent's tool surface shrinks by
four, sharpening tool selection. Existing/toolspresets that named the
old tools now nameinspect.
- The engine file READS are actor-only now.
- The service worker is slimmer and the worker bridges are unified. Two
internal refactors, no behavior change. The four hand-rolled worker↔host
bridges (OPFS, subagent, base-network reads, the a2a mesh) collapse into a
single factory, so adding the next one is a one-liner. And the service
worker sheds the clusters that were logic rather than wiring: the model
picker's catalog, the tab-strip affordances, and a couple of pure actor
kernels now live in their own small, tested modules, keeping the service
worker to assembly and routing.
Fixed
- WebVM now degrades gracefully on Firefox instead of showing a
misleading error. The in-browser Linux VM (CheerpX) needs
SharedArrayBuffer, which the browser grants only to a cross-origin-
isolated page, and a Firefox extension page can't isolate itself
(a Firefox platform limitation, not a peerd bug). The VM boot screen used
to fail with a "manifest must declare cross_origin_embedder_policy" error
that read like a config bug; on Firefox it now shows a plain-English notice
explaining the limitation, noting WebVM works in the Chrome build, and
linking the two open upstream threads (Bugzilla 1673477 and the W3C
WebExtensions issues) to push on.
Install (preview): Firefox: click the .xpi on this page. Chrome: drag
the .crx into chrome://extensions with Developer mode on. Most users
want the store packages (see the README). The auto-update feeds are
attached here and served at peerd.ai/updates/.
peerd-preview-v0.2.2
Added
- The dweb actor: a dedicated, opt-in mesh operator (preview only). When
the network is on, a second toggle spins up a persistent, keyless agent in
its own worker heap. It absorbs the dweb tools (discover / share / install /
block / peers / discovery), keeps a peer-and-publisher reputation ledger,
and monitors messages addressed to your agent, surfacing only what's
notable. It's addressable in chat asmessage_actor("dweb", …). It can
never be made to act by an inbound message: inbound turns may only observe,
use its own tools, and report. It installs or shares only with your
confirmation. With it on, the mesh tools leave the orchestrator entirely.
Mesh work is one delegation.
Fixed
- The global instance reads are now fenced.
js_read_fileand
app_read_filestay on the orchestrator, so files can be inspected cheaply
without an actor turn, but their content now comes back inside the
wrapUntrustedfence. An instance file is not reliably agent-authored:
notebook/app code fetches and persists web data. So an unfenced read was
the last remaining way for untrusted bytes to reach the orchestrator's
trusted context.js_rungets the same treatment: output from a
pure-compute run stays raw (it is the agent's own code), but a run that
calledpeerd.egress.fetchhas its value, console, and error text fenced.
Added
- Actor replies now surface in the chat as their own messages. When a
delegated actor (web / WebVM / Notebook / App) replies, the reply appears
at its place in the conversation as a quiet, attributed bubble ("notebook
actor · Esoteric Math"). Before, it was buried inside themessage_actor
tool card of an earlier turn. Failures show the same way, marked failed. - Notebook heatmap charts.
chart({ type: 'heatmap', data, x, y, v })
frompeerd:stdrenders a density grid: rows of{ x, y, v }bins shaded
byv. This was the missing chart kind that agents kept building with
hand-rolled Vega specs. - Notebook errors point at your code. A run error's stack now maps back
tonotebook.js:<line>(both in the output pane and in the agent's tool
result) instead of showing internal blob-URL frames. Applies to the
headlessjs_runpath too (job.js:<line>). - Notebook iteration feel. When a new run starts, the previous run's
output stays visible but dims. Consecutive runs read like a loop while
every realm stays fresh. The toolbar also gains apeerd:std ?cheat
sheet: the import line, the chart spec, and the full helper list at a
glance.
Changed
- The heap split. Every non-orchestrator agent loop now runs in its own
dedicated offscreen Worker heap. Bound actors (web / WebVM / Notebook /
App) and subagents (both tool-less reasoning and tool-bearing) run keyless,
in isolated memory. They reach the model and their tools only through
service-worker routes that re-check every call. Untrusted page, instance,
and response content stays in the actor's heap. It cannot reach the vault
key or the orchestrator's memory. The "actor fence" went from a prompt
boundary in one shared heap to a real memory boundary, the correct answer to
prompt injection: the loop that reads hostile content never holds the
authority to act on it. One substrate, one
code path: a subagent is an ephemeral actor, so the former reasoning and
actor stacks collapsed into one. Chrome-only (it needs the offscreen API);
Firefox falls back to the keyless in-SW loop until it has one.
Fixed
- A subagent could be granted the actor-only DOM/page tools (
read_page,
page_exec,click,navigate,fetch_url, …) and read or drive the
user's foreground tab. That is authority the main agent itself lacks. A
subagent's grantable toolset is now narrowed from the main-agent surface,
so it holds a subset of what its parent holds and delegates web/DOM work
to the web actor like the main agent does. - The vault-gate code-stream backdrop left faint lighter-than-black bands on
every row it had ever typed on (the alpha-wash fade only asymptotes toward
the background). The animation now redraws from state each frame and
trails decay to exactly zero. Idle rows are indistinguishable from
untouched background. - A Notebook run that returned a huge unrecognized object (e.g. a
hand-rolled Vega-Lite spec) dumped the entire JSON, hundreds of KB, into
the output pane, and the model's copy of the value was blind-truncated
mid-JSON. The pane dump is now capped with a note, and the tool result's
[VALUE]block is cut cleanly at the source, with an actionable
instruction to return a compact value or achart()/table()descriptor. peerd.self.import('peerd:std')failed ("cannot resolve"). The dynamic
import shim routed builtins through the OPFS compose path, where a builtin
has no file. Builtins now import their real URL directly, matching the
static resolver.
Install (preview): Firefox: click the .xpi on this page. Chrome: drag the .crx into chrome://extensions with Developer mode on. Most users want the store packages (see the README). The auto-update feeds are attached here and served at peerd.ai/updates/.
peerd-preview-v0.2.1
The code is the spec now: the standalone prose-doc corpus is removed. Deleted
DESIGN.md, ARCHITECTURE.md, ARCHITECTURE-CHANGES.md, MAP.md, FEATURES.md,
PACKAGING.md, STATUS.md, TODO.md, VERSIONING.md, CONTRIBUTING.md, the whole
docs/ tree of specs and distributed-design notes, and the per-module
DESIGN/DEV-NOTES files. Orientation now lives in README.md, CLAUDE.md, the
per-module READMEs, and the code. SECURITY.md and the store-listing and
compliance docs (docs/store/) are kept. No runtime behavior change; the
extension's user-facing behavior is identical to 0.2.0.
Removed
- The standalone prose design docs (see above). README.md and CLAUDE.md now
point at the per-module READMEs and the code instead of the deleted files.
Install (preview): Firefox: click the .xpi on this page. Chrome: drag the .crx into chrome://extensions with Developer mode on. Most users want the store packages (see the README). The auto-update feeds are attached here and served at peerd.ai/updates/.
peerd-preview-v0.2.0
This release restructures peerd's agent. It lands the staged backlog of
open PRs on one verified branch. Three things get better:
- Less context on the main agent. Each environment's operating
details (VM shell quirks, notebook isolation rules, app iframe gotchas,
whether to fetch or render a page) used to sit in the main prompt on
every turn, mostly unused. Now they live with the sub-agent that uses
them and load only when work is handed off. The main agent also drops
the web tools it no longer needs: web_search, read_article, call_api,
and submit_form all fold into the web actor. - Fewer tool calls. actor_list replaces five separate list tools with
one. The agent makes one call and carries one list instead of five.
message_actor's oneShot skips a whole model turn when one round of work
is enough. - Enforced isolation. The tools that operate environments are no
longer on the main agent at all, so even a confused or prompt-injected
agent can't reach them. It has to send a permission-gated message. Page
text, fetch bodies, and command output stay inside the sub-agent and
come back as a quoted, untrusted reply, never as raw text the agent
could be steered by.
Added
- The actor architecture (DESIGN-17 / DESIGN-18). The main agent now
acts as an orchestrator. It opens an environment (a WebVM, a notebook, a
built app, or the open web) and hands the work to that environment's own
sub-agent, called an actor, which holds only that environment's tools.
There is one way to delegate, message_actor. The web actor is the single
entry point for all web work and picks per task between a sessionless
fetch (fetch_url) and driving a real tab. Delegations run in the
background and in parallel. They show up as cards in the chat that you
can watch and stop, and they report their own cost. They survive a
service-worker restart because pending work is written to storage. - API integrations (origin actors). Send a message to a bare origin
like api.github.com and peerd forms a fetch-only, keyless,
origin-locked actor for it. The actor remembers what it learns about
that API across messages. - actor_list. One tool that lists everything you can message: every
WebVM, notebook, app, open tab, and API integration, each with its type
and the handle to pass to message_actor. Replaces five separate list
tools. - message_actor oneShot. Set it when one round of work settles the
request, like a specific command or a read. The actor does the action
and hands back the raw result instead of spending an extra model turn to
restate it. - PDF reading (
read_pdf). pdf.js text-layer extraction in the
offscreen document for born-digital PDFs. Runner-only, with output
wrapped as untrusted web content. - On-device OCR for scanned PDFs. The render→recognize pipeline is
wired (Tesseract).autoescalates when a PDF looks scanned and the
opt-in engine is installed. Fail-closed: it falls back to the text
layer until the driver is vendored and the asset SRIs are pinned. See
docs/PDF-READING.md. - Browser-native VM networking. Full HTTP, multi-host
git clone,
npm/pip/gem install via host-side resolution, and a response cache. - Session robustness. Auto-resume after a service-worker restart, a
per-message session store, and a provider failover chain. - Whole-extension type coverage.
// @ts-checkacross the
extension (100% of eligible files), enforced by a coverage floor. - Verbose VM diagnostics. The
devModesetting wires shell tracing
into the WebVM bridge.
Changed
- The main agent's browser tools are now just actor_list, open_tab, and
message_actor (plus capture). The low-level page tools and the tools
that write to an environment moved to the actors. - WebVM self-heal. When the browser freezes a backgrounded VM tab, peerd
now checks it and reloads it before a command lands on a dead shell. The
terminal output stripping was also fixed so output is not eaten when it
splits across a chunk. - The thinking and boot spinner is now the brand orb ring, one rainbow
sweep masked to a hollow ring. - The prose docs were removed. The code is the spec, and CLAUDE.md is the
short orientation map. - Service worker restructured into per-route modules with injected
per-module state stores; handlers stay thin. - README reordered to lead with install + project conventions; Tesseract
/ pdf.js / Gemma credited in the open-source list.
Fixed
- Settings normalizer now persists the web-write confirm + robustness
keys that were dropped during the route extraction.
Install (preview): Firefox: click the .xpi on this page. Chrome: drag the .crx into chrome://extensions with Developer mode on. Most users want the store packages (see the README). The auto-update feeds are attached here and served at peerd.ai/updates/.
peerd-preview-v0.1.5
A broad security-hardening pass across the sandbox, egress, runner, agent
loop, dweb transport, and engine registries. This release also adds the
autonomous e2e verify loop and the groundwork for the peerd-lite and
personal-data directions. Every code change went through adversarial-swarm
review passes (security fixes held to a no-residual-bypass bar) and was
verified green before merge.
Fixed
- Notebook realm seal now covers the Cache API. The sealed worker
also runs headless in the offscreenjs_runhost. That host's CSP
allowshttps:, soconnect-src 'none'did not backstop there.
CacheStorage.{open,match,has,delete,keys}are now sealed like the
other network primitives, so no network verb is reachable (#72). - Web-write "approve for this session" is scoped to the consented
host. The non-GET egress confirm named a specific host but cached
the grant by tool key alone, so one approval covered any host. The
grant key now includes the host (#73). - Browser-runner prompt-injection hardening. The disposable
do/get/check runner's prompt now names the<untrusted_web_content>
fence, calls out prompt injection as the attack vector, and adds an
IGNORE → FLAG → EXCLUDE drill with anti-suppression language. 6
contract tests cover each invariant (#81). - Stop is honored between tool-batch waves. A Stop (or spend-limit
halt) that lands mid-batch no longer lets queued write-tool waves
dispatch and commit side effects after the abort. The loop rechecks
before each wave and ends the turn as a deliberate stop (#97). - Agent-core input hardening. Four edge cases from a security audit:
the@filefence is defanged against break-out, theload_skill
version attribute is escaped, the SSE parser caps its buffers to
prevent OOM, and a non-string Anthropictool_usename now surfaces
an error instead of vanishing silently (#98). - dweb untrusted-inbound robustness (preview channel): guards on
the transport that reads directly from anonymous peers. Drop
unparseable data-channel frames and bound the pre-description ICE
buffer (#88), cap the declared bundle size before buffering as an OOM
guard (#89), make the DHTnode.handletotal over malformed RPC
(#90), and close theRTCPeerConnectionon an abandoned dial so it
can't leak (#91). - Engine registry races. Each registry's
load()is now memoized so
a cold-boot race can't drop a just-created record (#86), and Notebook
default-resolution is serialized per session so concurrent
first-commands don't double-create (#85).
Added
- Autonomous e2e verify loop.
bun run e2e:verifydrives the real
extension through every state on one Chrome (~6s). It writes a
screenshot per state plus a structuredresult.json, and a diff image
on a visual miss, so an agent can drive it unattended. Multi-turn,
mode-toggle, and vault-lock states were added (#70, #77). The
goal-state user-message assertion dropped in the consolidation was
restored (#76). Per-run artifacts are gitignored (#74). peerd:stdrecord helpers.parseJsonl/toJsonl/dedupeBy
for line-delimited records in code-mode (#92).- peerd-lite groundwork. A proof that the sealed Notebook substrate
runs verbatim in a plain web page with only a host adapter, under
web-prototype/poc/(#96). Also durable-OPFS round-trip coverage for
the on-device personal-data index (#93). - Design specs. The local-first personal-data agent (#92), the
peerd-web / peerd-lite surface (#84), and the site-as-demo reuse plan
(#87).
Changed
- Reader-facing docs de-jargoned: tighter voice, AI-isms removed.
Install (preview): Firefox: click the .xpi on this page. Chrome: drag the .crx into chrome://extensions with Developer mode on. Most users want the store packages (see the README). The auto-update feeds are attached here and served at peerd.ai/updates/.
peerd-preview-v0.1.4
Goal-mode hardening, side-panel state fixes, an end-to-end test tier, and
provider default-model selection. All changes were verified green and
reviewed by adversarial-swarm passes before merge.
Added
- Live-extension E2E tier. A reusable raw-CDP harness with goal /
stop / error scenarios. It runs the real chassis with the model faked
at the wire, and it is wired into CI (#57). It also adds a local,
npm-free visual-regression layer: self-contained PNG decode and pixel
diff against committed baselines, deliberately kept out of blocking
CI (#64). - Provider-aware default models and WebVM terminal fixes (#62).
Fixed
- Goal-mode autonomous loop. Durable Stop now reaches a
vault-lock-paused run. Resume sequencing and cap-boundary correctness
are fixed (#55). The Goal bar and Stop rehydrate when a surface
connects or reconnects mid-run (#59). Goal-resume is ordered before
auto-resume on interactive unlock, and durable-Stop is now awaited on
steer, new-chat, and archive (#63). - Spend-limit halt banner now persists across unrelated state pushes
(Plan/Act toggle,/system,/tools, settings) instead of vanishing
mid-halt (#54).
Changed
- README embeds the demo video after the intro (#65).
Install (preview): Firefox: click the .xpi on this page. Chrome: drag the .crx into chrome://extensions with Developer mode on. Most users want the store packages (see the README). The auto-update feeds are attached here and served at peerd.ai/updates/.