> ## 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.

# How the Livepeer Network Extends the Protocol

> The Livepeer Network is the off-chain execution layer that turns the Protocol's rules into a working open marketplace for video and AI compute. This guide explains where the protocol ends, where the network begins, and what the network adds on top.

export const StyledStep = ({title, icon, titleSize = 'h3', iconColor = null, titleColor = null, children, className = '', style = {}, ...rest}) => {
  const styledTitle = titleColor ? <span style={{
    color: titleColor
  }}>{title}</span> : title;
  return <Step title={styledTitle} icon={icon} iconColor={iconColor || undefined} titleSize={titleSize} className={className} style={style} {...rest}>
      {children}
    </Step>;
};

export const StyledSteps = ({children, iconColor, titleColor, lineColor, iconSize = '24px', className = '', style = {}, ...rest}) => {
  const resolvedIconColor = iconColor || 'var(--accent-dark, #18794E)';
  const resolvedTitleColor = titleColor || 'var(--lp-color-accent)';
  const resolvedLineColor = lineColor || 'var(--lp-color-accent)';
  return <div className={['docs-styled-steps', className].filter(Boolean).join(' ')} style={style} {...rest}>
      <style>{`
        .docs-styled-steps .steps > div > div.absolute > div {
          background-color: ${resolvedIconColor};
        }
        .docs-styled-steps .steps > div > div.w-full > p {
          color: ${resolvedTitleColor};
        }
        .docs-styled-steps .steps > div > div.absolute.w-px {
          background-color: ${resolvedLineColor};
        }
        .docs-styled-steps .steps > div:last-child > div.absolute.w-px::after {
          content: '';
          position: absolute;
          bottom: 0;
          left: 50%;
          transform: translateX(-50%);
          width: 6px;
          height: 6px;
          background-color: ${resolvedLineColor};
          transform: translateX(-50%) rotate(45deg);
        }
      `}</style>
      <div>
        <Steps>{children}</Steps>
      </div>
    </div>;
};

export const DynamicTableV2 = ({tableTitle = null, headerList = [], itemsList = [], monospaceColumns = [], columnWidths = {}, columnConfig = {}, showSeparators = false, margin, className = '', style = {}, ...rest}) => {
  if (!headerList.length) {
    return <div>No headers provided</div>;
  }
  const tableRef = useRef(null);
  const [measuredColumnWidths, setMeasuredColumnWidths] = useState({});
  const measureFitColumns = () => {
    const tableElement = tableRef.current;
    if (!tableElement) {
      return;
    }
    const nextWidths = headerList.reduce((accumulator, header, index) => {
      const config = columnConfig?.[header] || ({});
      if (!config.fitContent) {
        return accumulator;
      }
      const contentNodes = tableElement.querySelectorAll(`[data-docs-column-key="${index}"] [data-docs-fit-content]`);
      let maxContentWidth = 0;
      contentNodes.forEach(node => {
        const width = Math.ceil(node.getBoundingClientRect().width);
        if (width > maxContentWidth) {
          maxContentWidth = width;
        }
      });
      if (maxContentWidth > 0) {
        accumulator[header] = `${maxContentWidth + 16}px`;
      }
      return accumulator;
    }, {});
    setMeasuredColumnWidths(currentWidths => {
      const currentEntries = Object.entries(currentWidths);
      const nextEntries = Object.entries(nextWidths);
      if (currentEntries.length === nextEntries.length && nextEntries.every(([header, width]) => currentWidths[header] === width)) {
        return currentWidths;
      }
      return nextWidths;
    });
  };
  useLayoutEffect(() => {
    measureFitColumns();
  }, [headerList, itemsList, columnConfig]);
  useEffect(() => {
    const tableElement = tableRef.current;
    if (!tableElement || typeof ResizeObserver === 'undefined') {
      return undefined;
    }
    const resizeObserver = new ResizeObserver(() => {
      measureFitColumns();
    });
    resizeObserver.observe(tableElement);
    if (tableElement.parentElement) {
      resizeObserver.observe(tableElement.parentElement);
    }
    return () => {
      resizeObserver.disconnect();
    };
  }, [headerList, itemsList, columnConfig]);
  const fitHeaders = headerList.filter(header => columnConfig?.[header]?.fitContent);
  const hasMeasuredFitColumns = fitHeaders.length === 0 || fitHeaders.every(header => Boolean(measuredColumnWidths[header]));
  const getColumnStyle = (header, isMonospace = false) => {
    const config = columnConfig?.[header] || ({});
    const fitContent = Boolean(config.fitContent);
    const fluid = Boolean(config.fluid);
    const nowrap = Boolean(config.nowrap) || fitContent || isMonospace;
    const preferredWidth = columnWidths[header];
    const measuredWidth = measuredColumnWidths[header];
    return {
      ...fitContent && measuredWidth ? {
        width: measuredWidth,
        minWidth: measuredWidth,
        maxWidth: measuredWidth
      } : {},
      ...!fitContent && !fluid && preferredWidth ? {
        minWidth: preferredWidth
      } : {},
      ...nowrap ? {
        whiteSpace: 'nowrap'
      } : {
        wordWrap: 'break-word',
        overflowWrap: 'break-word'
      }
    };
  };
  const getColumnTrackStyle = header => {
    const config = columnConfig?.[header] || ({});
    const fitContent = Boolean(config.fitContent);
    const fluid = Boolean(config.fluid);
    const preferredWidth = columnWidths[header];
    const measuredWidth = measuredColumnWidths[header];
    if (fitContent && measuredWidth) {
      return {
        width: measuredWidth,
        minWidth: measuredWidth,
        maxWidth: measuredWidth
      };
    }
    if (fluid) {
      return {};
    }
    if (preferredWidth) {
      return {
        width: preferredWidth
      };
    }
    return {};
  };
  const renderCellContent = (header, content) => {
    const config = columnConfig?.[header] || ({});
    if (!config.fitContent) {
      return content;
    }
    return <div data-docs-fit-content style={{
      display: 'inline-flex',
      alignItems: 'center',
      whiteSpace: 'nowrap',
      width: 'max-content',
      maxWidth: 'none'
    }}>
        {content}
      </div>;
  };
  return <div className={className} style={style} {...rest}>
      {tableTitle && <div style={{
    fontStyle: 'italic',
    margin: 0
  }}>
          <strong>{tableTitle}</strong>
        </div>}
      <div style={{
    overflowX: 'auto',
    ...margin != null && ({
      margin
    })
  }} role="region" tabIndex={0} aria-label={tableTitle ? `Scrollable table: ${tableTitle}` : 'Scrollable table'}>
        <table ref={tableRef} data-docs-dynamic-table-v2 style={{
    width: '100%',
    tableLayout: hasMeasuredFitColumns ? 'fixed' : 'auto',
    borderCollapse: 'collapse',
    fontSize: '0.9rem',
    marginTop: 0
  }}>
          <colgroup>
            {headerList.map((header, index) => <col key={index} style={getColumnTrackStyle(header)} />)}
          </colgroup>
          <thead>
            <tr style={{
    backgroundColor: 'var(--lp-color-accent)',
    color: 'var(--lp-color-on-accent)',
    borderBottom: '1px solid var(--lp-color-border-default)'
  }}>
              {headerList.map((header, index) => <th key={index} data-docs-column-key={index} style={{
    padding: '10px 8px',
    textAlign: 'left',
    fontWeight: '600',
    color: 'var(--lp-color-on-accent)',
    verticalAlign: 'top',
    ...getColumnStyle(header)
  }}>
                  {renderCellContent(header, header)}
                </th>)}
            </tr>
          </thead>
          <tbody>
            {itemsList.filter(item => showSeparators || !item?.__separator).map((item, rowIndex) => item?.__separator ? <tr key={rowIndex} style={{
    backgroundColor: 'var(--lp-color-accent)',
    color: 'var(--lp-color-on-accent)',
    borderBottom: '1px solid var(--lp-color-accent)'
  }}>
                    <td colSpan={headerList.length} style={{
    padding: '6px 8px',
    fontWeight: '700',
    color: 'var(--lp-color-on-accent)',
    letterSpacing: '0.01em'
  }}>
                      {(item[headerList[0]] ?? item.Category) ?? 'Category'}
                    </td>
                  </tr> : <tr key={rowIndex} style={{
    borderBottom: '1px solid var(--lp-color-border-default)'
  }}>
                    {headerList.map((header, colIndex) => {
    const value = (item[header] ?? item[header.toLowerCase()]) ?? '-';
    const isMonospace = monospaceColumns.includes(colIndex);
    return <td key={colIndex} data-docs-column-key={colIndex} style={{
      padding: '8px 8px',
      fontFamily: isMonospace ? 'monospace' : 'inherit',
      verticalAlign: 'top',
      ...getColumnStyle(header, isMonospace)
    }}>
                          {renderCellContent(header, isMonospace ? <code>{value}</code> : value)}
                        </td>;
  })}
                  </tr>)}
          </tbody>
        </table>
      </div>
    </div>;
};

export const LinkArrow = ({href, label, description, newline = true, borderColor, className = '', style = {}, ...rest}) => {
  const linkArrowStyle = {
    display: 'inline-flex',
    alignItems: 'center',
    justifyContent: 'center',
    gap: "var(--lp-spacing-1)",
    width: 'fit-content',
    ...borderColor && ({
      borderColor
    })
  };
  return <span className={className} style={style} {...rest}>
      {newline && <br />}
      <span style={linkArrowStyle}>
        <a href={href} target="_blank" rel="noopener noreferrer">
          {label}
        </a>
        <Icon icon="arrow-up-right" size={14} color="var(--lp-color-accent)" />
      </span>
      {description && description}
      {description && <div style={{
    height: "var(--lp-spacing-3)"
  }} />}
    </span>;
};

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 Quote = ({children, className = "", style = {}, ...rest}) => {
  const quoteStyle = {
    fontSize: "1rem",
    textAlign: 'center',
    opacity: 1,
    fontStyle: 'italic',
    color: 'var(--lp-color-accent)',
    border: '1px solid var(--lp-color-border-default)',
    borderRadius: "8px",
    padding: "var(--lp-spacing-4)",
    margin: '1rem 0',
    ...style
  };
  return <blockquote className={className} style={quoteStyle} {...rest}>{children}</blockquote>;
};

export const CustomCardTitle = ({icon, title, variant = "card", iconSize, style = {}, className = "", ...rest}) => {
  const variants = {
    card: {
      display: 'flex',
      alignItems: 'center',
      gap: "var(--lp-spacing-2)",
      marginBottom: "var(--lp-spacing-3)",
      color: 'var(--lp-color-text-primary)',
      fontSize: '1rem',
      fontWeight: 600
    },
    accordion: {
      display: 'inline-flex',
      alignItems: 'center',
      gap: "var(--lp-spacing-2)"
    },
    tab: {
      display: 'inline-flex',
      alignItems: 'center',
      gap: '0.4rem',
      fontSize: '0.875rem'
    }
  };
  const sizes = {
    card: 20,
    accordion: 18,
    tab: 14
  };
  const size = iconSize || sizes[variant] || 20;
  const baseStyle = variants[variant] || variants.card;
  return variant === 'card' ? <div className={className} style={{
    ...baseStyle,
    ...style
  }} {...rest}>
      {typeof icon === 'string' ? <Icon icon={icon} size={size} color="var(--lp-color-accent)" /> : icon}
      {title}
    </div> : <span className={className} style={{
    ...baseStyle,
    ...style
  }} {...rest}>
      {typeof icon === 'string' ? <Icon icon={icon} size={size} color="var(--lp-color-accent)" /> : icon}
      {title}
    </span>;
};

<Quote>
  The Livepeer Protocol defines the rules. The Livepeer Network is what those rules produce - an open, off-chain marketplace where Gateways find Orchestrators, jobs are negotiated and dispatched, and the Protocol's settlement primitives turn off-chain work into on-chain payment.
</Quote>

<CustomDivider style={{ margin: 0, marginBottom: "-2rem" }} />

## The protocol-network boundary

The protocol enforces. The network executes. Each side owns a different set of responsibilities, and most operational confusion comes from blurring the line.

<DynamicTableV2
  headerList={["Concern", "Protocol owns it", "Network owns it"]}
  itemsList={[
{ "Concern": "Active set membership", "Protocol owns it": "Yes - sorted by total bonded stake on-chain", "Network owns it": "No" },
{ "Concern": "Round progression", "Protocol owns it": "Yes - RoundsManager advances rounds", "Network owns it": "No" },
{ "Concern": "Per-job orchestrator selection", "Protocol owns it": "No", "Network owns it": "Yes - gateway-side, off-chain" },
{ "Concern": "Capability advertisement", "Protocol owns it": "No", "Network owns it": "Yes - orchestrator HTTP endpoints" },
{ "Concern": "Price negotiation", "Protocol owns it": "No", "Network owns it": "Yes - gateway/orchestrator handshake" },
{ "Concern": "Job dispatch and segment delivery", "Protocol owns it": "No", "Network owns it": "Yes - direct gateway-to-orchestrator" },
{ "Concern": "Payment settlement", "Protocol owns it": "Yes - TicketBroker redeems winning tickets", "Network owns it": "Off-chain ticket issuance" },
{ "Concern": "Inflationary rewards", "Protocol owns it": "Yes - Minter and BondingManager", "Network owns it": "No" },
{ "Concern": "Governance outcomes", "Protocol owns it": "Yes - LivepeerGovernor executes", "Network owns it": "No" },
]}
/>

The protocol cares only about the things that need cryptographic finality. Everything that needs flexibility, negotiation, or low latency lives on the network.

<CustomDivider style={{ margin: "-1rem 0 -2rem 0" }} />

## What the network adds

The protocol's rules are general. The network is where those rules meet specific workloads, specific topologies, and specific demand patterns.

<StyledSteps iconColor="var(--accent)" titleColor="var(--accent)">
  <StyledStep title="Discovery and selection" icon="magnifying-glass">
    Gateways query the on-chain ServiceRegistry for the Active Set, then probe Orchestrators directly to read their advertised capabilities, prices, and availability. Selection is a Gateway decision - the protocol expresses eligibility, not preference.
  </StyledStep>

  <StyledStep title="Capability advertisement" icon="bullhorn">
    Each Orchestrator publishes the workloads it can serve - video transcoding profiles, AI inference pipelines, real-time AI sessions, BYOC containers. The advertisement is a bitstring that Gateways can filter against without an on-chain lookup.
  </StyledStep>

  <StyledStep title="Job dispatch" icon="paper-plane">
    Gateways send segments or inference requests directly to the chosen Orchestrator over HTTP, RTMP, or the trickle protocol for real-time AI. The network carries the data; the chain never sees it.
  </StyledStep>

  <StyledStep title="Real-time and BYOC pipelines" icon="bolt">
    The protocol's rules say nothing about pipelines. The network defines built-in pipelines for video transcoding and AI inference, plus a Bring-Your-Own-Container (BYOC) extension that lets developers attach new workload classes without a protocol change.
  </StyledStep>

  <StyledStep title="Off-chain coordination" icon="diagram-project">
    Gateway-Orchestrator handshakes, capability negotiation, and session establishment all happen off-chain at HTTP latency. The protocol's round timer (\~24 hours on Arbitrum) is far too slow for per-segment coordination - the network operates at frame and segment timescales.
  </StyledStep>
</StyledSteps>

<CustomDivider style={{ margin: "-1rem 0 -2rem 0" }} />

## How a job moves through the network

A typical video or AI job touches the chain only twice: once when the Gateway funds its TicketBroker deposit, and once when the Orchestrator redeems a winning ticket. Everything in between is the network.

<StyledSteps iconColor="var(--accent)" titleColor="var(--accent)">
  <StyledStep title="Gateway funds a deposit" icon="wallet">
    Gateway transfers ETH into the TicketBroker, establishing both a deposit (drawn against per ticket) and a reserve (collateral that proves the Gateway can pay).
  </StyledStep>

  <StyledStep title="Gateway selects orchestrators" icon="filter">
    Gateway reads the Active Set, queries candidates for capabilities and prices, applies its own performance criteria, and assembles a working set of Orchestrators for the session.
  </StyledStep>

  <StyledStep title="Job dispatch begins" icon="play">
    Gateway sends segments or inference inputs directly to the chosen Orchestrator over HTTP or the trickle protocol. Each segment carries an off-chain probabilistic micropayment ticket signed by the Gateway.
  </StyledStep>

  <StyledStep title="Orchestrator delivers output" icon="arrow-right-arrow-left">
    Orchestrator transcodes or runs inference, returns the output to the Gateway, and accumulates tickets locally.
  </StyledStep>

  <StyledStep title="Winning ticket settles on-chain" icon="ticket">
    When a ticket clears the winning probability threshold, the Orchestrator submits it to the TicketBroker, which pays out ETH from the Gateway's deposit. Most tickets never settle. The expected value across all tickets matches the work performed.
  </StyledStep>
</StyledSteps>

The network does not need to involve the chain to coordinate, dispatch, or deliver. It only needs the chain to hold the Active Set, the deposit, and the right to redeem.

<CustomDivider style={{ margin: "-1rem 0 -2rem 0" }} />

## Where the network goes beyond the original protocol

The original whitepaper described a video transcoding marketplace. The network has since extended itself - usually without changing the protocol - to handle:

* **AI inference**, sharing the same Orchestrator role and economic primitives but with different capability advertisements and pipeline shapes.
* **Real-time AI video**, over the trickle protocol, with frame-level latency budgets that the round-based protocol layer never needs to know about.
* **Bring-Your-Own-Container (BYOC) workloads**, where any developer can publish a container that Orchestrators run for arbitrary compute-bound tasks. The protocol still settles payment; the network defines the pipeline.
* **Special Purpose Entities (SPEs)**, which build network-layer features (real-time AI, BYOC frameworks, observability tooling) under treasury funding without needing a protocol upgrade.

The pattern is consistent: keep the protocol minimal, extend the network. New workloads, new pipelines, new participants - all added without a protocol change. The on-chain rules stay small. The off-chain marketplace grows.

<CustomDivider style={{ margin: "-1rem 0 -2rem 0" }} />

## Where to go next

<Columns cols={2}>
  <Card title={<CustomCardTitle icon="circle-nodes" title="Network Design" />} href="/v2/about/network/design" horizontal arrow>
    Canonical reference: network role, design properties, and actor map.
  </Card>

  <Card title={<CustomCardTitle icon="store" title="Marketplace Model" />} href="/v2/about/network/marketplace-model" horizontal arrow>
    Posted-price market shape, work and value flow, honesty enforcement.
  </Card>

  <Card title={<CustomCardTitle icon="diagram-project" title="Job Pipelines" />} href="/v2/about/network/job-pipelines" horizontal arrow>
    Workload classes, job lifecycle, built-in pipelines, BYOC.
  </Card>

  <Card title={<CustomCardTitle icon="sitemap" title="Network Architecture" />} href="/v2/about/network/architecture" horizontal arrow>
    Topology, traffic planes, on-chain anchor contracts, observability.
  </Card>
</Columns>
