> ## Documentation Index
> Fetch the complete documentation index at: https://docs.livepeer.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Pool Operators

> How to operate a Livepeer GPU pool – accept external worker connections, manage job routing across multiple GPUs, handle off-chain fee distribution, and maintain operational transparency.

export const CustomDivider = ({color = "var(--lp-color-border-default)", middleText = "", spacing = "default", style = {}, className = "", ...rest}) => {
  const spacingPresets = {
    default: {
      margin: "24px 0"
    },
    overlap: {
      margin: "-1rem 0 -1rem 0"
    },
    tight: {
      margin: "0 0 -1rem 0"
    },
    section: {
      margin: "0 0 -2rem 0"
    },
    sectionOverlap: {
      margin: "-1rem 0 -2rem 0"
    },
    deepOverlap: {
      margin: "-1rem 0 -1.5rem 0"
    }
  };
  const spacingStyle = spacingPresets[spacing] || spacingPresets.default;
  return <div role="separator" aria-orientation="horizontal" className={className} style={{
    display: "flex",
    alignItems: "center",
    ...spacingStyle,
    fontSize: style?.fontSize || "16px",
    height: "fit-content",
    ...style
  }} {...rest}>
      <span style={{
    marginRight: "var(--lp-spacing-px-8)",
    opacity: 0.2
  }}>
        <Icon icon="/snippets/assets/logos/Livepeer-Logo-Symbol-Theme.svg" />
      </span>
      <div style={{
    flex: 1,
    height: "1px",
    background: "var(--lp-color-border-default)",
    opacity: 0.4
  }}></div>
      {middleText && <>
          <Icon icon="circle" size={2} />
          <span style={{
    margin: "0 8px",
    fontWeight: "bold",
    color: color,
    opacity: 0.7
  }}>
            {middleText}
          </span>
          <Icon icon="circle" size={2} />
        </>}
      <div style={{
    flex: 1,
    height: "1px",
    background: "var(--lp-color-border-default)",
    opacity: 0.4
  }}></div>
      <span style={{
    marginLeft: "var(--lp-spacing-px-8)",
    opacity: 0.2
  }}>
        <span style={{
    display: "inline-block",
    transform: "scaleX(-1)"
  }}>
          <Icon icon="/snippets/assets/logos/Livepeer-Logo-Symbol-Theme.svg" />
        </span>
      </span>
    </div>;
};

export const TableCell = ({children, align = "left", header = false, style = {}, className = "", ...rest}) => {
  const Component = header ? "th" : "td";
  return <Component className={className} style={{
    padding: "0.75rem 1rem",
    textAlign: align,
    border: header ? "none" : "1px solid var(--lp-color-border-default)",
    ...style
  }} {...rest}>
      {children}
    </Component>;
};

export const TableRow = ({children, header = false, hover = false, style = {}, className = "", ...rest}) => {
  const rowId = `table-row-${Math.random().toString(36).substr(2, 9)}`;
  return <>
      {hover && <style>{`
          #${rowId}:hover {
            background-color: var(--lp-color-bg-card);
          }
        `}</style>}
      <tr id={rowId} className={className} style={{
    ...header && ({
      backgroundColor: "var(--lp-color-accent-strong)",
      color: "var(--lp-color-on-accent)",
      fontWeight: "bold"
    }),
    ...style
  }} {...rest}>
        {children}
      </tr>
    </>;
};

export const StyledTable = ({children, variant = "default", style = {}, className = "", ...rest}) => {
  const wrapperVariants = {
    default: {
      border: "1px solid var(--lp-color-border-default)",
      backgroundColor: "var(--lp-color-bg-card)",
      overflow: "hidden"
    },
    bordered: {
      border: "2px solid var(--lp-color-accent)",
      backgroundColor: "var(--lp-color-bg-page)",
      overflow: "hidden"
    },
    minimal: {
      border: "none",
      backgroundColor: "transparent",
      overflow: "visible"
    }
  };
  return <div data-docs-styled-table-shell className={className} style={{
    width: "100%",
    padding: 0,
    margin: 0,
    ...wrapperVariants[variant],
    ...style
  }} {...rest}>
      <table data-docs-styled-table style={{
    width: "100%",
    borderCollapse: "collapse",
    borderSpacing: 0,
    margin: 0,
    backgroundColor: "transparent"
  }}>
        {children}
      </table>
    </div>;
};

export const BorderedBox = ({children, variant = "default", padding = "var(--lp-spacing-4)", borderRadius = "var(--lp-spacing-px-8)", margin = "", accentBar = "", style = {}, className = "", ...rest}) => {
  const variants = {
    default: {
      border: "1px solid var(--lp-color-border-default)",
      backgroundColor: "var(--lp-color-bg-card)"
    },
    accent: {
      border: "1px solid var(--lp-color-accent)",
      backgroundColor: "var(--lp-color-bg-card)"
    },
    muted: {
      border: "1px solid var(--lp-color-border-default)",
      backgroundColor: "transparent"
    }
  };
  const accentBarColors = {
    accent: "var(--lp-color-accent)",
    positive: "var(--green-9)"
  };
  return <div data-docs-bordered-box="" data-accent-bar={accentBarColors[accentBar] ? "" : undefined} className={className} style={{
    ...variants[variant],
    padding: padding,
    borderRadius: borderRadius,
    ...margin ? {
      margin
    } : {},
    ...accentBarColors[accentBar] ? {
      position: "relative",
      '--accent-bar-color': accentBarColors[accentBar]
    } : {},
    ...style
  }} {...rest}>
      {children}
    </div>;
};

A Livepeer pool is a single Orchestrator node that routes jobs to multiple external GPU workers. You hold the on-chain identity and LPT stake; workers contribute GPU compute and earn payouts managed off-chain by you, the operator. This page is for experienced Orchestrators who want to expand beyond their own hardware by accepting external worker connections.

Workers joining an existing pool should start with [Join a Pool](/v2/Orchestrators/guides/deployment-details/join-a-pool).

<CustomDivider />

## How a pool works

The Livepeer Protocol sees only one entity: your Orchestrator. It has a single on-chain address, a single stake, and a single service URI. Everything behind that address is your architecture to design.

In a pool, the Orchestrator node accepts connections from remote transcoders (workers). When a Gateway routes a job to your Orchestrator, go-livepeer dispatches it to an available worker via gRPC streaming RPC. Workers process the segment and return results – the Orchestrator handles all protocol-level interaction, the workers handle the compute.

```mermaid theme={"theme":{"light":"github-light","dark":"dark-plus"}}
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#18794E', 'primaryTextColor': '#fff', 'primaryBorderColor': '#3CB540', 'lineColor': '#3CB540', 'mainBkg': '#18794E', 'nodeBorder': '#3CB540', 'clusterBkg': 'transparent', 'clusterBorder': '#3CB540', 'titleColor': '#3CB540', 'edgeLabelBackground': 'transparent', 'textColor': '#3CB540', 'nodeTextColor': '#fff'}}}%%
graph LR
  G["Gateway"] --> O["Orchestrator\n(on-chain, LPT staked)"]
  O --> W1["Worker 1\nGPU"]
  O --> W2["Worker 2\nGPU"]
  O --> W3["Worker 3\nGPU"]
```

Workers have no on-chain presence. Delegators and Explorer see only your Orchestrator identity, and all stake, protocol reputation, and on-chain fees flow through that address.

<CustomDivider />

## Worker connection models

<BorderedBox variant="accent" padding="16px">
  <Tabs>
    <Tab title="BYO Container" icon="microchip">
      Workers run go-livepeer in `-transcoder` mode directly – on bare Linux, in Docker, or in a VM – and connect using the `-orchSecret` you configure.

      **Who it suits:** Technically capable workers who want full control over their environment. Best for Linux operators already comfortable with GPU workloads.

      **Worker provides:** A machine with an NVIDIA GPU, NVIDIA drivers, and network connectivity to your Orchestrator on port 8935.

      **Worker runs:**

      ```bash icon="terminal" title="Start a worker in transcoder mode" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
      livepeer \
        -transcoder \
        -orchAddr <YOUR_ORCHESTRATOR_HOST>:8935 \
        -orchSecret <SHARED_SECRET> \
        -nvidia 0 \
        -maxSessions 10
      ```

      Workers do not need an Ethereum account, LPT, or an RPC endpoint. They connect, register, and begin receiving transcoding jobs automatically.

      **You configure:** Set `-orchSecret` on your orchestrator (see [Accepting workers](#accepting-workers)). Open port 8935 inbound for worker connections.
    </Tab>

    <Tab title="Managed pool client" icon="coins">
      Some pool operators build a custom client that wraps go-livepeer and adds payout tracking and a simplified UX. Titan Node, for example, publishes their own pool binary that workers download and configure with their ETH address and a nickname.

      **Who it suits:** Workers who want a simplified setup experience, particularly on Windows. Removes the requirement to install go-livepeer directly.

      **Worker provides:** GPU machine, NVIDIA driver, their Ethereum address for payout tracking, and sufficient upload bandwidth (100 Mbps+ recommended).

      **You build or provide:** A custom pool client is operator-built tooling. You are responsible for building or adapting client software, maintaining a payout dashboard, and tracking worker contributions by ETH address.

      <Note>
        Building and maintaining a custom pool client is a large engineering investment. Titan Node built and maintains their own pool binary. Operators starting out should usually begin with the BYO Container model because it requires far less custom tooling.
      </Note>
    </Tab>

    <Tab title="Cloud GPU" icon="server">
      Workers provision a cloud GPU instance (Vast.ai, Lambda Labs, CoreWeave, RunPod) and connect it as a remote transcoder. This requires no owned hardware – workers pay for GPU time and earn back through transcoding fees.

      **Who it suits:** Workers without dedicated hardware. Also useful for pool operators who need temporary capacity bursts.

      **You configure:** Same as BYO Container. Optionally provide a Docker image workers can pull to simplify cloud setup.

      <Note>
        Cloud GPU economics for transcoding are tight. Workers should verify margins on their chosen provider before committing. At current network pricing, high-end consumer GPUs on owned hardware are significantly more cost-efficient than rented compute.
      </Note>
    </Tab>
  </Tabs>
</BorderedBox>

<CustomDivider />

## Accepting workers

To configure your Orchestrator to accept remote worker connections, run **without** `-transcoder` but **with** `-orchSecret`:

```bash icon="terminal" title="Accept remote workers on the orchestrator" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
livepeer \
  -network arbitrum-one-mainnet \
  -ethUrl <RPC_URL> \
  -orchestrator \
  -orchSecret <SHARED_SECRET> \
  -serviceAddr <PUBLIC_HOST>:8935 \
  -pricePerUnit <PRICE_PER_UNIT>
```

**Key points:**

* **`-orchSecret`** is a shared secret that authenticates worker connections. Any node that knows this secret can connect as a worker. Treat it like a password.
* **`-transcoder` is omitted.** This puts the Orchestrator in standalone mode: it handles Gateway connections and routing, but does no local transcoding. All jobs go to connected workers.
* **Port 8935** must be open for both inbound Gateway connections and inbound worker connections.

<Warning>
  Keep `-orchSecret` private. If it is exposed, any node can connect as a worker and receive job assignments. Depending on your off-chain payout model, this could result in payout obligations to unknown parties or diluted job distribution.
</Warning>

The secret can be passed as plaintext or from a file (recommended):

```bash icon="terminal" title="Load the shared secret from a file" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
-orchSecret /path/to/secret.txt
```

When a worker connects successfully, your Orchestrator logs will show:

```icon="terminal" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
Got a RegisterTranscoder request from transcoder=10.3.27.1 capacity=10
```

The `capacity` field is the worker's `-maxSessions` value – how many concurrent jobs it can handle.

<CustomDivider />

## Fee distribution

<Note>
  **Fee distribution in a Livepeer pool is entirely off-chain.** The protocol pays all fees and rewards to your Orchestrator's Ethereum address. There is no protocol mechanism to split payments to workers automatically. Pool operators implement their own payout systems.
</Note>

**What the protocol does:** All ETH transcoding fees and LPT inflation rewards are paid to your Orchestrator's Ethereum address. The Reward Cut and fee share you configured applies at this level, splitting between you and your Delegators.

**What you manage:** Tracking worker contributions and distributing their share of earnings from your Orchestrator wallet. Common approaches:

<StyledTable variant="bordered">
  <TableRow header>
    <TableCell header>Approach</TableCell>
    <TableCell header>How it works</TableCell>
    <TableCell header>Suitable for</TableCell>
  </TableRow>

  <TableRow>
    <TableCell>Per-session tracking</TableCell>
    <TableCell>Log sessions handled per worker ETH address; pay proportionally at period end</TableCell>
    <TableCell>Small pools with a few trusted workers</TableCell>
  </TableRow>

  <TableRow>
    <TableCell>Custom pool client</TableCell>
    <TableCell>Build tooling that tracks address + nickname + sessions; automate periodic payouts</TableCell>
    <TableCell>Public-facing pools (Titan Node's model)</TableCell>
  </TableRow>

  <TableRow>
    <TableCell>Manual distribution</TableCell>
    <TableCell>Calculate shares manually; send ETH periodically</TableCell>
    <TableCell>Testing and very small pools only</TableCell>
  </TableRow>
</StyledTable>

Payout protocols remain operator-specific. Existing pools have each implemented their own approach. Public-facing pools should be explicit with workers about payout frequency, minimum thresholds, and how contributions are measured.

<CustomDivider />

## On-chain identity and transparency

Your pool is represented entirely by your Orchestrator's Ethereum address. On [Livepeer Explorer](https://explorer.livepeer.org):

* **Delegators** see your total stake, Reward Cut, Fee Cut, and historical performance
* **Explorer shows only the Orchestrator** – worker-level data stays in your off-chain systems.
* **Pool performance** (sessions, fees) reflects aggregate work done by all connected workers combined

If you are running a public pool, building Delegator trust requires transparency work beyond go-livepeer configuration. Titan Node does this via a public website, regular Livepeer Forum campaign posts, and a public payout dashboard. Consider documenting your pool's architecture, hardware footprint, uptime history, and payout record.

<CustomDivider />

## Ongoing operational responsibilities

<AccordionGroup>
  <Accordion title="Worker connection management" icon="gear">
    Monitor connected workers. If a worker disconnects, your Orchestrator continues accepting jobs and assigns them to remaining workers. A worker that was mid-session when it disconnected will cause that session to fail and the segment to be retried by the Gateway.

    Workers reconnect automatically on restart. You will see a new `Got a RegisterTranscoder request` log line each time. There is no manual reconnection step required on your side.
  </Accordion>

  <Accordion title="NVENC session caps on consumer GPUs" icon="server">
    Consumer NVIDIA GPUs (GTX/RTX series) have a hardware-enforced limit on concurrent NVENC encoding sessions – typically 3–8 per card depending on the model. Workers hitting this limit will reject new segments.

    Titan Node patches the NVIDIA driver on worker machines to remove this cap. Operators who want higher worker concurrency should either communicate this limitation early or provide driver-patching instructions.
  </Accordion>

  <Accordion title="Session routing and load balancing" icon="circle-question">
    go-livepeer distributes sessions across connected workers internally. No manual load balancing is required for basic deployments. For large pools, a load balancer in front of multiple Orchestrator instances is possible – see `doc/multi-o.md` in the go-livepeer repository for the multi-Orchestrator architecture.
  </Accordion>

  <Accordion title="Node updates and downtime" icon="server">
    Updating go-livepeer on the Orchestrator drops all connected workers and in-flight sessions. Gateways will observe the interruption. Workers reconnect automatically after the Orchestrator restarts.

    For pools with SLA commitments, coordinate updates during low-traffic periods and communicate planned downtime to workers in advance.
  </Accordion>

  <Accordion title="Payout and worker communication" icon="circle-question">
    Establish a clear communication channel with your workers – a Discord server, Telegram group, or mailing list. Workers need timely notice of: planned downtime, payout schedule, fee changes, and how to report connection issues.

    Poor communication is the most common cause of worker churn in community pools.
  </Accordion>

  <Accordion title="orchSecret rotation" icon="circle-question">
    If you need to rotate your `-orchSecret` (for example, because you believe it has been compromised), all existing worker connections will drop immediately when the Orchestrator restarts with the new secret.

    There is no zero-downtime rotation mechanism. Communicate the new secret to all workers before restarting the Orchestrator. Workers reconnect automatically with the new secret once updated.
  </Accordion>
</AccordionGroup>

<CustomDivider />

## Key facts to remember

<CardGroup cols={2}>
  <Card title="One entity on-chain" icon="fingerprint">
    Your pool is one Orchestrator address. Workers are invisible to the protocol. All reputation, stake, and on-chain fees flow to you.
  </Card>

  <Card title="Fee distribution is your problem" icon="coins">
    The protocol does not split fees to workers. You track contributions and pay from your wallet. Build or adopt tooling before onboarding workers.
  </Card>

  <Card title="-orchSecret is the gate" icon="key">
    Anyone with your `-orchSecret` can connect as a worker and receive jobs. Keep it private. Rotate it if compromised.
  </Card>

  <Card title="Workers need nothing on-chain" icon="server">
    Workers do not need LPT, an Ethereum account for protocol purposes, or an RPC endpoint. They contribute compute only.
  </Card>
</CardGroup>

<CustomDivider />

<CardGroup cols={2}>
  <Card title="Join a Pool" icon="users" href="/v2/orchestrators/guides/deployment-details/join-a-pool">
    The worker perspective – connecting your GPU to an existing pool.
  </Card>

  <Card title="Split O-T Setup" icon="plug" href="/v2/orchestrators/guides/deployment-details/siphon-setup">
    The Orchestrator-transcoder split that underpins pool architecture.
  </Card>

  <Card title="Fleet Operations" icon="building" href="/v2/orchestrators/guides/advanced-operations/scale-operations">
    Running multiple Orchestrators at data-centre scale.
  </Card>

  <Card title="Earnings and Economics" icon="chart-line" href="/v2/orchestrators/guides/staking-and-rewards/earning-model">
    Pool economics, fee strategy, and what to expect from transcoding revenue.
  </Card>
</CardGroup>
