// documentation

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.

01, quickstart

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.

02, the five modules

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/tags and 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.

default modelsclaude-sonnet-4-6 · openai/gpt-4o-mini · qwen3:8b
reasoning effortmedium by default, dialable low→max per chat
prompt caching3 of 4 Anthropic breakpoints (system, tools, history)
retriesHTTP backoff + initial-connect retry; mid-stream drops fail honestly
pricingstatic table + local math; no price feed, no telemetry

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.

// security: the key is the asset
  • 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-access acknowledgement, 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 Authorization header 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's curl/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.

passphrase KDFArgon2id only (64 MiB / 3 iters); PBKDF2 removed
auto-lock45 min idle (settable; 0 = never)
denylist164 patterns · 8 categories · user overlay
confirm timeout120 s → auto-deny; broken channel → instant deny
audit logappend-only, 20k-entry cap, oldest pruned first
storage tierschrome.storage.local · IndexedDB · session (RAM)
// security: what is hard, stated honestly
  • codeNo exfiltration over the credentialed path. safeFetch refuses any origin off the allowlist and forces redirect: 'manual', throwing on any 3xx. Bare fetch is 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.

headlessjs_run · sealed worker, no tab, ephemeral scratch
notebookjs_notebook · fresh sealed Worker per run, OPFS file tree, realm-sealed network
appopaque-origin sandboxed iframe, OPFS-backed
vm shell timeout60 s default, 300 s max, per-VM command queue
vm diskIDB overlay over byte-range base; TOFU image pin
engine tools6 vm · 7 js · 10 app, all instance-addressable by name
// security: each environment, stated honestly
  • codeHeadless leans on the seal alone. js_run's offscreen host needs network for other features, so it has no connect-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 fetch with the audited bridge (the native is deleted off the prototype chain) and makes XMLHttpRequest, WebSocket, EventSource, importScripts, and nested workers throw. The one network path left is that audited bridge, and in a Notebook tab the page's connect-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 own fetch is 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/wget are GET-only and git is 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 import from any CDN (module loads are script-src, not the sealable connect-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.
// security: the lethal trifecta, structurally broken
  • 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.
// security: "but it's not isolated like Cloudflare"

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 did with 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.

// security: verify before you trust
  • 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.
03, faq

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.