# Workers



A Pear application splits into two halves:

1. a UI (the renderer, terminal, or mobile shell) and
2. a **worker** that owns everything peer-to-peer.

The worker is a [Bare](/bare/reference/bare/modules) process the host spawns at startup, and it's where [Hyperswarm](/p2p/reference/building-blocks/hyperswarm), [Corestore](/p2p/reference/helpers/corestore), [Hypercore](/p2p/reference/building-blocks/hypercore), and any native addons run.

The two halves only ever see each other through a single [Inter-Process Communication (IPC)](/pear/explanation/workers#the-ipc-contract) duplex stream.

<Callout type="info">
  This page is about the pattern, not the API. For the call signatures and IPC stream type, see [Running workers in the runtime reference](/pear/reference/pear/runtime#running-workers).
</Callout>

## Workers as a local backend [#workers-as-a-local-backend]

The mental model is **the worker is your application's local backend**. The renderer is a client to that backend in the same way a web app is a client to a remote API—it makes requests, it gets streaming events back, it doesn't own state. The difference is the "server" is a sibling process on the same machine, started and stopped by the host.

In a desktop Pear app the host is the Electron main process; in a terminal app it's the [`pear`](/pear/reference/pear/cli) CLI itself. Mobile has no separate host process—the view starts the worker (a worklet, there) directly; see [Pear mobile application architecture](/pear/explanation/pear-mobile-architecture#where-this-differs-from-desktop) for why. The worker code is identical across all of them—only the UI half and the bridge that forwards [IPC](/pear/explanation/workers#the-ipc-contract) change.

## Why the split [#why-the-split]

Three concrete reasons to put peer-to-peer code in a worker rather than inline in the UI:

### Native addons stay out of the renderer [#native-addons-stay-out-of-the-renderer]

[Hypercore](/p2p/reference/building-blocks/hypercore), [Hyperswarm](/p2p/reference/building-blocks/hyperswarm), [sodium-native](https://www.npmjs.com/package/sodium-native), and many other Pear building blocks load native modules. Electron's renderer cannot load native code under `sandbox: true`, and a `sandbox: false` renderer is a large attack surface. **Keeping native code in the worker keeps the renderer untrusted.**

### The renderer becomes portable [#the-renderer-becomes-portable]

Because the worker never imports DOM APIs and the UI never imports [Corestore](/p2p/reference/helpers/corestore) or Hyperswarm, you can swap the UI for a different framework (or a different platform's UI entirely) without rewriting the peer-to-peer logic. **This is what makes Keet's identical experience across desktop, mobile, and terminal possible.**

### One IPC channel, one place to audit [#one-ipc-channel-one-place-to-audit]

Every byte that crosses from peers into your application crosses the IPC boundary first. Validation, framing decisions, rate limits, and observability all sit at that one chokepoint instead of being scattered across the UI.

<Callout type="info">
  See [Runtime and languages](/pear/explanation/runtime-and-languages#one-codebase-many-platforms-the-pear-end-pattern) for the wider "Pear-end / UI" framing and why it's the recommended app shape.
</Callout>

## The IPC contract [#the-ipc-contract]

The host spawns a worker by calling [`PearRuntime.run`](/pear/reference/pear/runtime#running-workers)—the static method an Electron main process uses—or its instance alias [`pear.run`](/pear/reference/pear/runtime#running-workers), passing the worker's entrypoint and a list of arguments:

```js
const IPC = pear.run('./workers/main.js', [pear.storage])
IPC.on('data', (data) => {
  console.log('data from worker', data)
})
IPC.write('hello')
```

`IPC` is a [duplex stream](https://nodejs.org/api/stream.html#class-streamduplex): bytes the host writes show up on the worker side, and bytes the worker writes show up here. Inside the worker the other side of that same stream is `Bare.IPC`:

```js
const Corestore = require('corestore')
const storage = Bare.argv[2]

Bare.IPC.on('data', (data) => console.log(data.toString()))
Bare.IPC.write('Hello from worker')

const corestore = new Corestore(storage)
// ... open Hypercores, replicate over Hyperswarm, etc.
```

A few things about that snippet aren't obvious if you're new to Bare:

### `Bare.argv` indexing matches Node's `process.argv` [#bareargv-indexing-matches-nodes-processargv]

* `Bare.argv[0]` is the Bare binary,
* `Bare.argv[1]` is the worker script path, and
* `Bare.argv[2]` is the first argument *you* passed.
  Anything beyond that lands at `Bare.argv[3]`, `[4]`, and so on.

### The IPC stream carries bytes, not objects [#the-ipc-stream-carries-bytes-not-objects]

There's no built-in JSON or length-framing. Either send one message per `write()` (Bare's IPC preserves write boundaries on the receiving side when the message is small enough not to be split), or pick a framing format like newline-delimited JSON or [compact-encoding](/p2p/reference/helpers/compact-encoding) and stick to it on both ends.

### `pear.storage` is the recommended first argument [#pearstorage-is-the-recommended-first-argument]

It points at a per-app directory the host has already prepared (see [Storage and distribution](/pear/explanation/storage-and-distribution)). **Worker code stays portable as long as it treats `Bare.argv[2]` as "wherever the host says my storage is" rather than hard-coding a path.**

<Callout type="info">
  Raw IPC carries bytes, not typed messages. Once you outgrow hand-pairing requests and responses, [Structured RPC and schema-first design](/bare/explanation/rpc-and-schemas) covers `bare-rpc`, `hrpc`, and the `hyperschema`/`hyperdb`/`hyperdispatch` toolchain for generating the wire format from one declaration.
</Callout>

## Where the boundary should sit [#where-the-boundary-should-sit]

A pragmatic rule of thumb:

* Anything that touches peers, storage, or cryptography belongs on the worker side;
* Anything that touches a screen, a keyboard, or a window belongs on the UI side.

<Callout type="info">
  When in doubt, ask "could this code run unchanged if I swapped Electron for a terminal UI?"—if yes, it's worker code; if not, it's UI code.
</Callout>

The renderer in a typical Pear Electron app, then, owns very little: layout, event listeners, and a thin transport that posts user actions to the worker and renders whatever streams back. The worker is where the application actually lives.

## Lifecycle and multiple workers [#lifecycle-and-multiple-workers]

A host can run more than one worker. Common patterns:

* One **main worker** that owns the application's primary append-only logs and swarm membership. This is the usual shape and what [Reshape into a production app](/p2p/getting-started/build-a-peer-to-peer-chat/reshape-into-a-production-app) (part 2 of the getting started path) builds.
* Additional **ephemeral workers** for one-off jobs—a heavy import, a video transcode, a synchronous CPU task—that exit when their work is done. These keep the main worker's event loop free.

Each worker is its own [Bare](/bare/reference/bare/modules) process, has its own [`IPC`](/pear/reference/pear/runtime#running-workers) stream on the host side—the duplex returned by [`PearRuntime.run`](/pear/reference/pear/runtime#running-workers)—and can be torn down independently. The host is responsible for cleaning up on Electron's [`before-quit`](https://www.electronjs.org/docs/latest/api/app#event-before-quit) event; see how [`getWorker()`](/p2p/getting-started/build-a-peer-to-peer-chat/reshape-into-a-production-app#read-the-electron-main-process) registers an `app.on('before-quit', …)` listener in the pear-chat tutorial for one way to wire that up.

## See also [#see-also]

* [Running workers in the runtime reference](/pear/reference/pear/runtime#running-workers)—covers `pear.run`, the `IPC` duplex stream, and `Bare.IPC` on the worker side.
* [Structured RPC and schema-first design](/bare/explanation/rpc-and-schemas)—HRPC, bare-rpc, and the schema toolchain for when raw IPC message-pairing gets error-prone.
* [Add typed RPC to a Pear app](/p2p/how-to/add-typed-rpc-to-a-pear-app)—the worked how-to for the HRPC pattern.
* [HRPC reference](/p2p/reference/helpers/hrpc)—full API for the recommended RPC layer.
* [Runtime and languages](/pear/explanation/runtime-and-languages)—the broader "Pear-end / UI" pattern, Bare's role, and how non-JavaScript code joins in via native addons.
* [Pear desktop application architecture](/pear/explanation/pear-desktop-architecture)—how workers, the preload bridge, and the OTA updater fit together in a real Electron app.
* [Storage and distribution](/pear/explanation/storage-and-distribution)—where `pear.storage` actually points on each operating system.
* [Reshape into a production app](/p2p/getting-started/build-a-peer-to-peer-chat/reshape-into-a-production-app)—a tutorial that wires the hello-pear-electron-shaped `pear-chat` worker behind a `window.bridge` API.
* [Corestore reference](/p2p/reference/helpers/corestore)—the storage factory typically opened on `Bare.argv[2]` inside a worker.
* [Compact encoding reference](/p2p/reference/helpers/compact-encoding)—binary framing for IPC messages crossing the host/worker boundary.
* The upstream source: [`hello-pear-electron`'s Workers](https://github.com/holepunchto/hello-pear-electron#workers).
