What peerd is, precisely.
peerd is an agent harness that runs as a browser extension. The agent works entirely client-side, in your tabs, with your sessions and your own model key, talking to whichever provider you pick. No backend, no account, no telemetry. Apache 2.0.
The full harness runs with no external process outside the browser runtime. Context management, tool calling, sub-agent spawning, memory, planning, execution. There is no Bun server, no Go binary, no Python sidecar, no Docker container, no companion app. The extension is the entire harness.
Workload execution stays in-browser too. The primitive is the V8 isolate the browser already ships, the same one Cloudflare runs agents on at datacenter scale, hardened by browsers for decades against hostile code. peerd wraps it into Linux VMs (CheerpX on WebAssembly), Notebooks (a sealed Web Worker plus OPFS), and Apps (sandboxed iframes), each in its own tab, not a remote container in the cloud and not a native process on your machine. Nothing in a tab reaches your host filesystem; the browser doesn't expose one.
Two browsers running peerd find each other and communicate directly over WebRTC. This P2P network is the same architecture as the rest of peerd: in the browser, on browser primitives, with nothing else in the path.
This strict model inherits everything the browser has already shipped: the Chrome (or Firefox) sandbox, Content Security Policy, WebCrypto, WebAuthn passkeys, Subresource Integrity, IndexedDB, WebRTC, and more. peerd writes zero lines of cryptographic code, zero lines of sandboxing code, and zero lines of process-isolation code. peerd is a bet that the open web is the natural infrastructure for an ecosystem of open AI agents.
Get going.
Install
peerd is pre-release, so today the install path is
load-unpacked: clone
the repo,
open chrome://extensions, enable Developer Mode,
and Load Unpacked on the extension/ directory.
There is no build step, the extension runs as written. Chrome
Web Store and Firefox Add-ons submissions are pending; signed
preview builds with auto-update will follow on GitHub Releases.
(On macOS and Windows, Chrome hard-disables off-store
.crx files even with policy, so load-unpacked is
the path there regardless.)
Unlock the vault
First run is passkey-first: peerd enrolls a WebAuthn passkey (Touch ID, Windows Hello, or a security key) that unwraps an encrypted vault holding your API keys. A recovery passphrase is optional and can be added later. The vault auto-locks after 45 minutes idle. Nothing leaves your machine.
Add a provider key
In Options → Providers, paste a key for Anthropic or OpenRouter, it is encrypted into the vault, and peerd only ever shows a masked preview. Or run Ollama locally and skip keys entirely; peerd reads your installed models and recommends one that fits your GPU. API keys are deliberately not part of onboarding.
First task
Open the side panel, type what you want done, and the agent works. New sessions start in Plan mode, it can read pages and load URLs but not click, type, or submit until you switch to Act. A single confirm toggle decides whether side effects pause for your sign-off. Behind the chat, the agent reads pages through a disposable runner (see runtime) and can spin up Linux VMs, Notebooks, and Apps (see engine) as it goes.
The architecture is the wordmark.
Five modules, one per letter, each a real subsystem in the codebase. The dependency graph runs left to right: provider, egress, and engine are Layer 1; runtime is Layer 2, composing them; distributed is Layer 3.
The connection to whichever model you picked. Bring your own key; peerd has no inference service of its own and never proxies your traffic through anyone.
Three adapters, one interface
Three model adapters ship today, all behind a tiny registry and
a single callModel() entry point:
- Anthropic: native
/v1/messages, the default. Streaming, prompt caching, and adaptive extended thinking. - OpenRouter: the OpenAI-compatible gateway, so one adapter reaches most vendors at once.
- Ollama: keyless and local on
localhost:11434. peerd reads your real installed models from/api/tagsand recommends one sized to your GPU.
A native OpenAI adapter does not exist yet (OpenRouter covers it meanwhile). Every adapter normalizes to one provider-agnostic stream of events (text, reasoning, tool-use, usage, stop), so the runtime never knows which vendor it is talking to.
Extended thinking is requested in the shape each model expects (modern models take an adaptive budget drawn from a 64k output ceiling; older ones take an explicit token budget). Effort defaults to medium, deliberately below the platform default, because long invisible deliberation reads as a hang in a browser. A per-chat dial raises it where the provider honors it.
- codeKeys live only in the vault. Adapters receive an injected
getSecret()and look the key up per call, never module state, never plaintext on disk. Only adapters ever touch a key; no page-facing tool does. - codeCredentialed traffic is allowlist-gated. Provider calls can only reach five frozen origins (Anthropic, OpenAI, OpenRouter, and Ollama on localhost / 127.0.0.1). Anything else throws and is audited. Redirects fail closed, a 3xx is by definition a hop to an unchecked origin.
- platformBYOK is explicit. The Anthropic adapter sends the
dangerous-direct-browser-accessacknowledgement, peerd is designed exactly for client-side keys in an encrypted vault, with every request mediated by the service worker. - codeHonest limit. The allowlist governs only the credentialed path. The agent's open-web tools (see egress) are denylist-gated, not allowlist-gated, so the mitigation against exfiltration there is architectural: the key never enters those code paths, and the page-reading runner has no network tools at all.
- codeCredential isolation by construction. The decrypted key lives only in the service worker, built into the
Authorizationheader there, held in memory, never written to disk in plaintext, never handed out. No execution sandbox (Notebook, App iframe, WebVM, headless worker) ever receives the key or an environment variable carrying it; each reaches the network only by relaying through the egress gate. There is nothing in a sandbox to intercept, because the secret was never placed where untrusted code runs. Isolation by architecture, not a credential proxy bolted in front of it.
The module where security is the whole point. Every secret, every outbound request, and every irreversible action passes through here.
The vault
One AES-GCM data key wraps everything sensitive. That key is itself wrapped by two interchangeable unlock factors: a WebAuthn passkey (full-entropy, no password) and an optional Argon2id passphrase. Argon2id is the only passphrase KDF; there is no weaker legacy path. The unwrapped key lives in service-worker memory and is mirrored only to RAM-backed session storage, so it survives the browser killing the worker but is gone when you close the browser.
Two egress gates
peerd splits the network into two regimes, because credentialed provider traffic and open-web browsing are different problems:
safeFetch: the credentialed path. A five-origin exact-match allowlist (no wildcards, by design). This is where your API keys go.webFetch: the open-web path for the agent's read tools and the VM'scurl/wget/git. No allowlist (the web is too big), but a strict gauntlet: scheme check, a private-network SSRF block, the sensitive-site denylist, redirect fail-closed, and an audit entry on every call, successes included.
The denylist
164 seed patterns across 8 categories (banks, brokers, crypto
exchanges, wallets, US health portals, government, password
managers, identity providers), plus your own additions and
removals, edited with search and confirm-to-remove. The matcher
only accepts exact hostnames or boundary-safe *.
globs, so *.chase.com can never be tricked into
matching evilchase.com. It gates both the agent's
tool dispatch and webFetch.
- codeNo exfiltration over the credentialed path.
safeFetchrefuses any origin off the allowlist and forcesredirect: 'manual', throwing on any 3xx. Barefetchis forbidden project-wide by lint, only the vetted wrappers can reach the network. - codeNo LAN or loopback access from web tools. A private-network gate runs ahead of the denylist and is thorough: RFC1918, loopback, link-local, and the encoded forms attackers reach for (decimal/hex/octal IPv4, IPv4-mapped and NAT64 IPv6). Stated non-claims, in the code's own comments: DNS rebinding is not client-side blockable, and exfiltration to an arbitrary public domain is out of scope here (the runner architecture handles that, see runtime).
- codeSensitive origins are unreachable. The denylist is checked at the dispatcher and again inside
webFetch, so a malformed tool can't slip past it. It loads before any tool can run. - codeA side effect can never proceed silently, and a confirm can never hang a turn. The confirmation coordinator auto-denies on a closed panel and after 120 s, fail-closed in both directions.
- platformThe unwrapped key never lands on disk. It exists only in worker memory and RAM-backed session storage, cleared by the platform on browser close. At rest it is only ever ciphertext under Argon2id or a passkey.
The module that creates and manages the agent's execution environments. The primitive underneath every one is the JS isolate the browser already ships.
A V8 isolate (a SpiderMonkey realm on Firefox) starts in milliseconds and isolates strongly, which is why Cloudflare builds its agent platform on them rather than containers or VMs. The browser has shipped that same isolate for decades, hardened against hostile code from every site you load. peerd runs the agent's code there: no server, no container, no process on your machine, and nothing that can reach a host filesystem the browser never exposes.
An execution environment is one isolate plus the tooling around it (a shell, a filesystem, a UI), picked for what a job needs. They range from a sealed JS worker, the lightest, for the agent's own code, to an opaque-origin iframe, the strongest boundary, for code you'll share. Most run in a tab you can watch; one runs headless.
Headless worker: the agent's own quick compute
The lightest environment, and the base the rest build on: a sealed JS
worker with no tab (js_run), running in the background.
Each call is fresh and ephemeral, its scratch discarded after, leaner
and quicker than a tab and never stealing focus, so it is where the
agent runs its own throwaway work. This is peerd's code
mode: instead of a long chain of separate tool or MCP calls,
the agent writes one script that loops, fetches, and returns just the
result, over the same audited HTTP bridge
(peerd.egress.fetch, the gate call_api uses).
The same worker also runs compiled wasm32-wasi
binaries via the peerd:wasi builtin — query a
SQLite file, decode an archive, run a language runtime — against an
in-memory filesystem: the module gets no network at all, only the
stdin and files the call hands it.
The run is recorded in the audit log like any other tool call.
Notebook: a JS workspace you watch
The same sealed worker, now in a tab you watch
(js_notebook): a CodeMirror file-tree editor backed by OPFS, so you see every run. Each run is a fresh
worker (no warm kernel, no leftover globals, no cells), so a
run is a pure function of the code plus the files on disk, reproducible
by construction (none of Jupyter's works-only-if-you-run-it-in-order
surprises). State that must outlive a run goes to OPFS explicitly;
dependencies are explicit imports (the peerd:std stdlib,
peerd:wasi for compiled WASI binaries, or a CDN URL),
never ambient globals. Return a value and the pane renders
it: a flat-object array becomes a table, a chart spec draws
an SVG.
App: a client-side SPA
A step up in isolation: multi-file HTML/CSS/JS the agent writes for you,
stored in OPFS and run in a sandboxed opaque-origin
iframe, the strongest boundary peerd has: its own origin, every
chrome.* API stripped. That is what makes it the safe host
for code you'll share or receive, and a dwapp is just
an App distributed over the dweb (see distributed).
The agent can rewrite an App in place and the tab live-reloads.
WebVM: a real Linux box
The heaviest environment: a full Debian, compiled to WebAssembly with
CheerpX. The tab is the VM: a persistent bash
shell and an xterm terminal, exit codes captured inline.
The disk is an IndexedDB overlay over a streamed base image, so the
blocks you touch cache locally and the next boot is offline. There are
no raw sockets (the browser has none to give), so its only network is
curl/wget/git wrappers that relay
through the extension's egress stack.
WebAssembly is the general lever here. A whole Linux box in a tab is just the first runtime peerd points it at; the same WASM confinement can host other heavy workloads (a language VM, a database, a native toolchain), so a new one is a matter of compiling it in, not inventing a new kind of sandbox.
js_run · sealed worker, no tab, ephemeral scratchjs_notebook · fresh sealed Worker per run, OPFS file tree, realm-sealed network- codeHeadless leans on the seal alone.
js_run's offscreen host needs network for other features, so it has noconnect-src 'none'backstop, one layer thinner than a Notebook tab, which keeps it scoped to the agent's own code (untrusted code belongs in an App's iframe). Egress is still the audited bridge, so headless runs aren't untracked. - codeThe Notebook is realm-sealed. The worker's first import replaces
fetchwith the audited bridge (the native is deleted off the prototype chain) and makesXMLHttpRequest,WebSocket,EventSource,importScripts, and nested workers throw. The one network path left is that audited bridge, and in a Notebook tab the page'sconnect-src 'none'CSP backs it up. - platformApps run with zero
chrome.*access. The runner page is sandboxed in the manifest, which strips every extension API and gives it an opaque origin. Honest caveat: an App's ownfetchis the least-fenced surface here, it rides the iframe's normal CORS network and isn't egress-audited. - platformThe WebVM has no raw sockets. CheerpX's emulated kernel has no TCP/UDP at all, no SSH, no port scan, no C2. Its only egress is the bash wrappers, which relay to
webFetch(SSRF block + denylist + audit);curl/wgetare GET-only andgitis archive-download only. - platformTrust-on-first-use disk integrity. Before CheerpX opens the base image, peerd hashes its first 64 KiB; a later mismatch fails the boot closed, guarding against silent corruption of cached overlay blocks.
- modelKnown open channel. Notebook code can still
importfrom any CDN (module loads arescript-src, not the sealableconnect-src), so code can be pulled un-audited. Documented plainly rather than papered over.
The orchestrator the side panel is a view over. The loop, the tools, the memory, and the disposable runner that keeps untrusted page text away from everything valuable.
The loop
A streaming, abortable tool-use loop. Read-class tool calls run as concurrent waves; writes stay strictly serial. Each session has its own stop control, so steering one chat never disturbs a stream in another. Long sessions roll up into a trim summary; a crashed worker recovers from per-delta persistence.
do / get / check: the only browser surface
This is the load-bearing security decision in the whole product.
The main agent has no direct DOM tools at all.
To act on a page it calls do, get, or
check, each of which spawns a disposable
browser-runner: a fresh sub-agent with its own
context, pinned to one tab, holding no memory, no keys, no
network tools, no code-eval, and no ability to spawn. The runner
drives the page and returns a plain-text summary. Untrusted page
content and your privileged capabilities are never in the same
model context.
Tools, gated
56 tools are registered; 44 are visible to the main agent and 12 (the low-level DOM and page tools) are runner-only, filtered from the main agent's tool list and refused at dispatch if it names one anyway. Every call runs a six-gate chain (persona, exposure, origin, confirmation, egress, audit) with the full gate lineage attached to the result, so the UI can show exactly what a call passed through.
Plan / Act, and one toggle
Safety is two controls. Plan lets the agent read and load URLs but never click, type, or submit; Act enables the rest. A single confirm toggle decides whether side effects pause for your sign-off. Plan/Act plus the denylist carry the weight.
And the rest of the brain
- Memory: file-based, AGENTS.md-style, hierarchical scope. New: auto-memory proposes a few notes at wrap-up that you approve or dismiss, never written silently.
- Commands & skills: slash commands,
@-references, a fuzzy palette, and user-authored skills loaded on demand via progressive disclosure. - Tool manifests: per-session tool exposure (
/tools), inherited by every sub-agent as a narrowing, never an escalation. - Hooks: pre/post tool-use, fail-closed; a pre-hook is the last veto before a side effect, even over a human yes.
- Voice: local Moonshine transcription with a Web Speech fallback, a hard mic kill switch, and a no-speech watchdog.
- Sub-agents, clock, cost, review, Ralph loop: depth-bounded sub-agents, temporal grounding, per-turn cost metering with a hard spend cap, a clean-context reviewer, and a persistent loop driver.
- codeThe runner breaks the trifecta. Private data, untrusted content, and an outbound path are the three legs of the classic exfiltration attack. peerd keeps them out of one context: the runner that reads untrusted pages has no memory, no keys, no egress, no spawn, no code-eval, and one pinned tab. Even fully prompt-injected, it has nothing to steal and nowhere to send it.
- codeRunner-only tools are unreachable from main. Two layers: the 12 hidden tools are filtered from the advertised list and refused at dispatch when the turn is a main turn. A prompt-injected model emitting a hidden tool name gets nothing.
- codeFences can't be forged from inside. Page text and runner summaries are wrapped in
<untrusted_web_content>/<untrusted_runner_summary>, and any embedded closing tag is HTML-encoded so hostile content can't terminate its own fence and smuggle instructions out. - modelTreating fenced content as data is model discipline. The structural fence raises the bar; honoring it is the model's job. This is the one probabilistic layer, and we label it as such.
- codeMemory writes always confirm. Persistent state is the trifecta's "private data" leg, so writing memory always shows a diff and asks, and that confirmation can't be toggled off.
- codeResults are redacted before they persist. Screenshots collapse to a metadata sentinel and long outputs are head/tail-truncated before being stored and re-sent, keeping image tokens and bloat out of context.
Fair question, and worth answering straight. Cloudflare hands every agent its own V8 isolate because they're running thousands of strangers' code on shared hardware, the isolate is a wall between tenants. peerd is one person's browser. There's no second tenant to wall off, so an isolate-per-subagent would be a fence down the middle of an empty room.
- codeSubagents are reasoning loops, not code sandboxes. A subagent (the do/get/check runner included) is an agent loop that runs on the service worker's thread and shares its heap. We're not going to pretend each one is its own isolate, because it isn't, and saying so wouldn't make it true.
- codeA separate heap does nothing about the actual threat. The thing that attacks an agent is prompt injection, which is just text. Poisoned page content reads exactly the same inside a pristine isolate as outside one. You don't fence injection with an address-space wall; you fence it by making sure the thing reading the poison has no keys, no egress, no spawn, no code-eval. That holds on a shared thread just as well as it would in its own isolate.
- platformWhere actual code runs, it does get a real boundary. The Cloudflare comparison is literal for the place it belongs: the agent's own scripts run in a language-sealed worker, code built for you or pulled peer-to-peer runs in an opaque-origin iframe, a POSIX program runs WASM-confined in a Linux VM. That's the V8 isolate (and friends), picked per trust level. The reasoning loop isn't untrusted code, so it isn't what we spend an isolate on.
- codeThe runner's context is capability-stripped by construction. We don't lean only on "the runner can't name a dangerous tool." A narrowed subagent's context object has the dangerous closures themselves removed (no secret access, no egress fetch, no spawn) for every capability none of its granted tools use. So the page-reading runner shares a heap with the agent, but its context literally holds no path to the vault or the network: even a bug that confused a DOM tool would have nothing in scope to reach.
- codeWhat we won't oversell. Shared heap means cross-session data isolation is enforced in our code, not by hardware; a pathological crash degrades the shared worker rather than one box; and subagents interleave on one thread instead of running on N cores. All true. None of it is the injection, capability-misuse, or credential-theft threat that an agent actually faces, and all three of those are handled by boundaries an extra isolate wouldn't add.
- codeThe short version. Cloudflare isolates agents from each other. peerd isolates the agent from its own blast radius. Different problem, and on the part where the comparison is fair (running code), we use the same primitive.
The one bet, made concrete: a real peer-to-peer network that lives in the browser. Every bit of routing, messaging, and storage logic runs in the page; the only servers are stateless bootstrap nodes that introduce two peers and then leave the data path. Built and tested today in the preview channel, the store build prunes this module entirely.
A network, and apps on top of it
peerd-distributed is two layers. A base network
does the universal work, find other peers, learn who they are and
what they can do, keep a healthy set of connections, and it is
designed to run always-on in the extension's offscreen document
so it outlives any single tab. Apps (dwapps) plug into
it: each gets a scoped capability,
peerd.distributed.dwapp, to open a
sub-protocol, a private overlay on the shared connections
where it speaks its own message types, be that chat, a shared
document, or game state. A "room" is just a sub-protocol; the
platform carries the bytes and never reads them.
What runs today
- Identity: native Ed25519 encoded as a
did:key, private half non-extractable, seeded from the vault so it persists across restarts. - Content addressing:
peerd://<did>/<hash>URIs over signed manifests that commit to an ordered list of 256 KiB chunk hashes, so one signature covers every byte. Apps travel as signed bundles and install from a peer, verified chunk by chunk. - The mesh: an N-peer WebRTC mesh of authenticated links. Each link proves the peer's
didwith a signed handshake before it joins, and frames are multiplexed by channel (control, DHT, direct, gossip). Rooms assemble three ways, a stateless rendezvous node, a relay through an existing member (so the room keeps working if the rendezvous dies, the "kill the server" beat), or an out-of-band paste-code. Trickle ICE connects on the first working path. - Messaging: two shapes. Gossip floods a signed message to the whole room, deduped and rate-limited. Direct messages go to exactly one peer over only that peer's link and are never forwarded, private at the routing layer and carried by WebRTC's encrypted channel. Plus presence, who's here right now.
- The DHT: a Kademlia variant running over the same mesh, a distributed addressable directory. Records are signed and mutable with a monotonic no-downgrade sequence, so a stale value can't be replayed, and routing tables are reachable-only (a contact is added only after it answers, the fix for browser NAT churn). It is where a late joiner finds a dwapp or a peer it missed in the gossip wave.
- commons, the demo dwapp: one room code, a public chat for everyone, and genuinely-private one-to-one chats behind a request/accept handshake. It ships pre-loaded in the Library, tagged
dweb, and travels peer-to-peer like any signed bundle.
How we know it works
The node logic is a pure actor: it only ever sends and receives opaque messages over its links, never reaching around the network. That makes the whole network simulatable, we run the real logic of many nodes against an in-memory transport, deterministically, and watch gossip flood, DHT lookups converge, and partitions heal. The same node code runs over real WebRTC in the browser and over a relay between separate OS processes, so a five-node network can be stood up and tested for real, not mocked.
Discovery and Sybil, honestly
Discovery is gossip-first, fast and churn-resilient, with the DHT
for durability and stateless bootstrap gateways as the cold-start
seed (bootstrap.peerd.ai is live). Sybil resistance
without a consensus mechanism is an unsolved problem, and peerd
doesn't pretend otherwise: signed identities stop forgery,
local-only peer scoring and source diversity raise the cost of
abuse, and an address book bucketed against any one source resists
eclipse, but a determined attacker with many identities can still
flood the network. That limit is stated, not hidden.
- codeA bundle is verified before any byte is used. The address must equal the recomputed manifest hash; the manifest's Ed25519 signature must check against the publisher's
did:key; every chunk is re-hashed on arrival; the reassembled size must match. Note: a publisher-less manifest is integrity-verified but makes no authorship claim, "verified bytes" is not "verified author." - codeSignatures can't be replayed across purposes. Every signed payload is domain-tagged (
peerd/manifest/v1,peerd/envelope/v1,peerd/dht-item/v1), and the canonical encoder rejects non-integer numbers so a non-reproducible signature is impossible. - codeA direct message is private, but not yet end-to-end encrypted. It travels over only the recipient's link and is never relayed, so no third peer ever sees the bytes, and the WebRTC channel encrypts the hop. A sealed end-to-end layer lands with offline store-and-forward, where a relay would hold the ciphertext, that's the point at which it's needed. The platform never reads message bytes either way.
- codeA peer only serves what you announced. The content store has no pass-through cache; it serves a chunk only if it belongs to a manifest you explicitly announced, so accidental possession can't become accidental serving.
- platformA hostile bundle is inert data. This module only moves and verifies bytes; nothing here evaluates content. A received App is rendered in the engine's opaque-origin sandbox, so a malicious dwapp is confined exactly like any other App, and reaching the network goes through a granted, quota'd capability, never a raw key.
- codeThe store build contains zero dweb code. Three CI gates enforce it: a boundary checker, a build-time prune of the module and its prompt text, and a re-verification of the final artifact that greps for any remaining reference. The dweb is opt-in even on preview, and the agent never gets dweb tools.
Frequently asked.
Is peerd released? Where do I get it?
Not in a store yet, peerd is 0.x experimental beta. Install
it today by cloning
the repo
and loading the extension/ directory unpacked in
Chrome (Developer Mode). There's no build step. Chrome Web
Store and Firefox submissions, plus signed auto-updating
preview builds, are pending.
Which models does peerd support?
Anthropic (native), OpenRouter (which fronts most vendors), and a keyless local Ollama. A native OpenAI adapter is planned. You bring your own key for the cloud providers; Ollama needs none. Adding a provider is on the order of fifty lines, since every adapter normalizes to one streaming interface.
How does the agent read web pages without getting prompt-injected?
The main agent never reads raw page content. It issues intent
(do / get / check) and a
disposable runner, fresh context, no memory, no keys, no
network, one pinned tab, does the reading and hands back a
summary. Page text is fenced as untrusted data the whole way.
So even a successful injection lands in a context that has
nothing worth stealing and no way out. See
runtime.
Can the Linux VM make network connections?
Over HTTP(S) only, and through the same egress gate the agent
uses, curl, wget, git,
package installs all work. What's structurally impossible:
raw sockets, SSH, ICMP, anything below the HTTP layer, the
browser gives WebAssembly no socket to open. See
engine.
Why doesn't peerd support MCP?
Because the browser already is the tool surface. Tabs give the
agent every app you're logged into; fetch gives it
every API with your credentials; the WebVM gives it a full
shell; WebRTC gives it peer-to-peer. An MCP layer would add
configuration and an exfiltration vector the egress model
exists to close, for no capability gain.
What's the dweb / distributed module, and is it on?
A real browser-to-browser network: cryptographic identity, an
N-peer WebRTC mesh, gossip and genuinely-private 1:1 messaging,
a Kademlia DHT for discovery, and apps (dwapps) that run
peer-to-peer, the first being commons, a shared
chat. It's real and tested but ships only in the
preview channel: the store build structurally prunes
the entire module. It's opt-in even on preview, and the agent
never gets dweb tools. See distributed.
Does voice work offline? Where does my data go?
After a one-time model download (Moonshine, SRI-verified, cached locally), transcription is fully local, audio never leaves your browser; a Web Speech fallback exists for browsers without local support. More broadly: your conversations and keys never leave your machine except as the calls you make to your own model provider. No telemetry, ever.
Does peerd work in Firefox?
Chrome and Chromium browsers are the primary target. Firefox parity work has landed (including a no-CDP DOM snapshot fallback), and preview builds are AMO-signed. The architectural primitives peerd relies on exist in both engines: Firefox's SpiderMonkey provides the equivalent of V8 isolates (called Realms), tab process isolation via Fission, the same WebExtensions API surface, and the same WebAssembly runtime. peerd's behavior is the same; only the JS engine underneath differs. Safari needs a different extension model and has no timeline.
Can I contribute?
Yes. The repo is at github.com/NotASithLord/peerd with the architecture and design docs, a test suite that runs both headless and in-browser, and a live eval harness. CLA required.