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

# Network Core Mechanisms

> How the Livepeer Network coordinates work off-chain: orchestrator discovery, capability advertising, session selection, job dispatch, ticket plumbing, settlement triggers, and the round-driven heartbeat.

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>;
};

export const ScrollableDiagram = ({children, title = '', maxHeight = '500px', minWidth = '100%', showControls = false, className = '', style = {}, ...rest}) => {
  const buildDiagramKey = (currentTitle = '', currentClassName = '') => {
    const source = `${currentTitle}|${currentClassName}|scrollable-diagram`;
    let hash = 0;
    for (let index = 0; index < source.length; index += 1) {
      hash = hash * 31 + source.charCodeAt(index) >>> 0;
    }
    return `docs-diagram-${hash.toString(36)}`;
  };
  const diagramKey = buildDiagramKey(title, className);
  const zoomName = `${diagramKey}-zoom`;
  const zoomLevels = [{
    label: '75%',
    value: 0.75
  }, {
    label: '100%',
    value: 1
  }, {
    label: '125%',
    value: 1.25
  }, {
    label: '150%',
    value: 1.5
  }];
  const containerStyle = {
    overflow: 'auto',
    maxHeight,
    border: '1px solid var(--lp-color-border-default)',
    borderRadius: "8px",
    padding: "var(--lp-spacing-4)",
    background: 'var(--lp-color-bg-card)',
    position: 'relative'
  };
  return <div className={className} style={{
    position: 'relative',
    marginBottom: "var(--lp-spacing-4)",
    ...style
  }} {...rest}>
      {title && <p style={{
    textAlign: 'center',
    fontStyle: 'italic',
    color: 'var(--lp-color-text-secondary)',
    marginBottom: "var(--lp-spacing-2)",
    fontSize: '0.875rem'
  }}>
          {title}
        </p>}

      {showControls ? <style>{`
          [data-docs-diagram-key="${diagramKey}"] [data-docs-diagram-content] {
            transform: scale(1);
            transform-origin: top left;
            width: max-content;
          }
          ${zoomLevels.map(zoomLevel => `
          #${diagramKey}-${zoomLevel.label.replace('%', '')}:checked ~ [data-docs-diagram-shell] [data-docs-diagram-content] {
            transform: scale(${zoomLevel.value});
          }
          #${diagramKey}-${zoomLevel.label.replace('%', '')}:checked ~ [data-docs-diagram-controls] label[for="${diagramKey}-${zoomLevel.label.replace('%', '')}"] {
            background: var(--lp-color-accent);
            color: var(--lp-color-on-accent);
            border-color: var(--lp-color-accent);
          }`).join('\n')}
        `}</style> : null}

      {showControls ? zoomLevels.map(zoomLevel => {
    const inputId = `${diagramKey}-${zoomLevel.label.replace('%', '')}`;
    return <input key={inputId} id={inputId} type="radio" name={zoomName} defaultChecked={zoomLevel.value === 1} style={{
      position: 'absolute',
      opacity: 0,
      pointerEvents: 'none'
    }} />;
  }) : null}

      <div data-docs-diagram-key={diagramKey} data-docs-diagram-shell style={containerStyle}>
        <div data-docs-diagram-content style={{
    minWidth,
    transformOrigin: 'top left',
    width: 'max-content'
  }}>
          {children}
        </div>
      </div>

      {showControls ? <div data-docs-diagram-controls style={{
    display: 'flex',
    justifyContent: 'flex-end',
    alignItems: 'center',
    gap: "var(--lp-spacing-2)",
    marginTop: "var(--lp-spacing-2)",
    flexWrap: 'wrap'
  }}>
          <span style={{
    fontSize: "0.75rem",
    color: 'var(--lp-color-text-muted)',
    marginRight: 'auto'
  }}>
            Scroll to pan
          </span>
          {zoomLevels.map(zoomLevel => {
    const inputId = `${diagramKey}-${zoomLevel.label.replace('%', '')}`;
    return <label key={inputId} htmlFor={inputId} style={{
      background: 'transparent',
      color: 'var(--lp-color-text-secondary)',
      border: '1px solid var(--lp-color-border-default)',
      borderRadius: "4px",
      padding: '4px 10px',
      cursor: 'pointer',
      fontSize: "0.75rem",
      fontWeight: '600'
    }}>
                {zoomLevel.label}
              </label>;
  })}
        </div> : null}
    </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 Subtitle = ({style = {}, text, children, variant = 'default', fontSize = '', fontWeight = '', fontStyle = '', marginTop = '', marginBottom = '', color = '', className = '', ...rest}) => {
  const renderInlineCode = (value, keyPrefix) => {
    return value.split(/(`[^`]+`)/g).map((segment, index) => {
      if (segment.startsWith('`') && segment.endsWith('`')) {
        return <code key={`${keyPrefix}-code-${index}`}>{segment.slice(1, -1)}</code>;
      }
      return segment;
    });
  };
  const renderInlineMarkup = (value, keyPrefix = 'subtitle') => {
    if (typeof value !== 'string') {
      return value;
    }
    return value.split(/(\*\*[\s\S]+?\*\*)/g).map((segment, index) => {
      if (segment.startsWith('**') && segment.endsWith('**')) {
        const inner = segment.slice(2, -2);
        return <strong key={`${keyPrefix}-strong-${index}`}>
            {renderInlineCode(inner, `${keyPrefix}-strong-${index}`)}
          </strong>;
      }
      return renderInlineCode(segment, `${keyPrefix}-${index}`);
    });
  };
  const renderContent = (value, keyPrefix) => {
    if (Array.isArray(value)) {
      return value.map((item, index) => renderContent(item, `${keyPrefix}-${index}`));
    }
    return renderInlineMarkup(value, keyPrefix);
  };
  const variants = {
    default: {
      fontSize: '1rem',
      fontStyle: 'italic',
      color: 'var(--lp-color-accent)',
      marginBottom: 0
    },
    changelog: {
      fontSize: '0.8rem',
      fontStyle: 'normal',
      fontWeight: 700,
      color: 'var(--lp-color-text-primary)',
      marginBottom: 0
    }
  };
  const base = variants[variant] || variants.default;
  return <span className={className} style={{
    ...base,
    ...fontSize ? {
      fontSize
    } : {},
    ...fontWeight ? {
      fontWeight
    } : {},
    ...fontStyle ? {
      fontStyle
    } : {},
    ...marginTop ? {
      marginTop
    } : {},
    ...marginBottom ? {
      marginBottom
    } : {},
    ...color ? {
      color
    } : {},
    ...style
  }} {...rest}>
      {renderContent(text, 'text')}
      {renderContent(children, 'children')}
    </span>;
};

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 Network's mechanics live off-chain. Discovery, capability matching, session selection, job dispatch, and ticket plumbing all run between Gateway and Orchestrator without touching the chain. The chain comes in at three triggers: deposit, winning-ticket redemption, and per-round reward.
</Quote>

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

## Mechanism Map

Seven mechanisms run continuously across the Network. Each answers a different coordination problem; none requires central scheduling. The on-chain Protocol is referenced for ground truth (stake, deposits, registry) but is not in the per-job hot path.

<DynamicTableV2
  headerList={['Mechanism', 'What it coordinates', 'Where it runs']}
  itemsList={[
{
  Mechanism: <Subtitle variant="changelog">**Orchestrator Discovery**</Subtitle>,
  'What it coordinates': 'Gateway finds candidate Orchestrators by reading the on-chain registry and the operator pool',
  'Where it runs': 'Gateway-side, against subgraph or `ServiceRegistry`',
},
{
  Mechanism: <Subtitle variant="changelog">**Capability Advertising**</Subtitle>,
  'What it coordinates': 'Orchestrator publishes its capability bitstring, model loadouts, prices, and ticket parameters',
  'Where it runs': 'Orchestrator-side, served via `OrchestratorInfo` over gRPC',
},
{
  Mechanism: <Subtitle variant="changelog">**Session Selection**</Subtitle>,
  'What it coordinates': 'Gateway scores candidates, filters on capability and price, opens a session with the chosen Orchestrator',
  'Where it runs': 'Gateway-side, parallel `GetOrchestratorInfo` RPC',
},
{
  Mechanism: <Subtitle variant="changelog">**Job Dispatch**</Subtitle>,
  'What it coordinates': 'Per-segment or per-frame work hand-off, with a ticket attached to each unit',
  'Where it runs': 'Gateway-to-Orchestrator HTTP for batch; trickle protocol for real-time',
},
{
  Mechanism: <Subtitle variant="changelog">**Ticket Plumbing**</Subtitle>,
  'What it coordinates': 'Off-chain construction, signing, validation, and accumulation of probabilistic micropayment tickets',
  'Where it runs': 'In-band with each segment; signed by Gateway, validated by Orchestrator',
},
{
  Mechanism: <Subtitle variant="changelog">**Settlement Triggers**</Subtitle>,
  'What it coordinates': 'When the off-chain layer reaches into the on-chain Protocol: Gateway funding, winning-ticket redemption, per-round reward',
  'Where it runs': 'On Arbitrum One; triggered by Gateway or Orchestrator based on local state',
},
{
  Mechanism: <Subtitle variant="changelog">**Round Heartbeat**</Subtitle>,
  'What it coordinates': 'How the off-chain Network reacts to round transitions: Active Set lock, reward call window, parameter freeze',
  'Where it runs': 'Detected off-chain via `RoundsManager`; observed by every participant',
},
]}
/>

The first five mechanisms run thousands of times per minute across the Network. The last two are anchor points: settlement happens occasionally, the heartbeat once per round.

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

## Property Provenance

Every property the Network preserves traces back to a specific protocol decision: a Livepeer Improvement Proposal that ratified the change, and a contract or codebase that implements it. The provenance is what lets a researcher cite the running Network with confidence.

<DynamicTableV2
  headerList={['Network property', 'Ratified by', 'Implemented in']}
  itemsList={[
{
  'Network property': <Subtitle variant="changelog">**Off-chain settlement (PM tickets)**</Subtitle>,
  'Ratified by': 'Streamflow spec (2019), refined by LIP-36 cumulative earnings, LIP-52 snapshot claim',
  'Implemented in': '`pm/` package in `go-livepeer`; `TicketBroker` contract on Arbitrum One',
},
{
  'Network property': <Subtitle variant="changelog">**Active Set election**</Subtitle>,
  'Ratified by': 'Streamflow spec; LIP-83 round-length adjustment',
  'Implemented in': '`BondingManager`, `RoundsManager` contracts',
},
{
  'Network property': <Subtitle variant="changelog">**Service URI advertisement**</Subtitle>,
  'Ratified by': 'LIP-9 (ServiceRegistry)',
  'Implemented in': '`ServiceRegistry` contract; `discovery/` package in `go-livepeer`',
},
{
  'Network property': <Subtitle variant="changelog">**On-chain governance**</Subtitle>,
  'Ratified by': 'LIP-89, LIP-91, LIP-92 (Delta upgrade, 2023)',
  'Implemented in': '`LivepeerGovernor`, `BondingVotes`, `Treasury` contracts',
},
{
  'Network property': <Subtitle variant="changelog">**Arbitrum deployment**</Subtitle>,
  'Ratified by': 'LIP-73 (Confluence, 2022)',
  'Implemented in': 'Full contract redeployment to Arbitrum One',
},
{
  'Network property': <Subtitle variant="changelog">**SPE funding convention**</Subtitle>,
  'Ratified by': 'LIP-90 (Funding Entity Conventions, 2023)',
  'Implemented in': 'Social convention; Treasury proposals route through SPEs',
},
{
  'Network property': <Subtitle variant="changelog">**AI extension and BYOC**</Subtitle>,
  'Ratified by': 'AI Video SPE Stage 1 through Stage 4 (LIP-90 convention, 2023 to 2024); LIP-100 Cascade (proposed May 2025)',
  'Implemented in': '`ai/`, `byoc/`, `trickle/` packages in `go-livepeer`; `ai-runner` repository',
},
]}
/>

AI extension was bootstrapped without a dedicated LIP. From 2023, the LIP-90 SPE convention authorised AI work by funding the AI Video Special Purpose Entity through Stages 1 to 4, demonstrating a deliberate property of the SPE model: pipeline-level capability does not need a hard fork. LIP-100 (Cascade, proposed May 2025) later formalised AI-first Gateways as a protocol-level upgrade, ratifying what the SPE pipeline had already shipped.

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

## Orchestrator Discovery

Discovery is the first thing a Gateway does. It reads the on-chain registry to get a list of operator addresses, then resolves each address to a service URI and asks for capability and price.

The discovery loop lives in `discovery/discovery.go`. A Gateway holds an `OrchestratorPool` of `[]*url.URL` candidate Orchestrators, loaded from the Livepeer subgraph or supplied as a static list. The Gateway calls `GetOrchestratorInfo` on each in parallel.

<DynamicTableV2
  headerList={['Step', 'What happens']}
  itemsList={[
{
  Step: <Subtitle variant="changelog">**1. Pool load**</Subtitle>,
  'What happens': 'Gateway loads candidate Orchestrator URIs from the subgraph (`Transcoder.serviceURI`) or from configured static list',
},
{
  Step: <Subtitle variant="changelog">**2. Parallel probe**</Subtitle>,
  'What happens': 'Gateway issues `GetOrchestratorInfo` to every candidate concurrently. Initial timeout is 3 seconds; if no candidate responds, the timeout doubles up to a `maxGetOrchestratorCutoffTimeout` of 6 seconds',
},
{
  Step: <Subtitle variant="changelog">**3. Latency sort**</Subtitle>,
  'What happens': 'Successful responses are sorted ascending by measured RPC latency. Suspended Orchestrators (those that previously failed jobs) are filtered through `suspensionqueue`',
},
{
  Step: <Subtitle variant="changelog">**4. Pool refresh**</Subtitle>,
  'What happens': 'The candidate list refreshes periodically; Orchestrators that drop out of the Active Set are removed when their stake falls below the threshold',
},
]}
/>

The pool is a moving target. Active Orchestrators come and go each round; suspensions clear after a cooldown. Discovery reruns on every new session and on a periodic refresh.

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

## Capability Advertising

Capability advertising is what an Orchestrator publishes so a Gateway can decide whether to send work. The advertisement is the response to `GetOrchestratorInfo` and carries everything the Gateway needs to evaluate the operator.

<DynamicTableV2
  headerList={['Field', 'What it carries']}
  itemsList={[
{
  Field: <Subtitle variant="changelog">**Capabilities**</Subtitle>,
  'What it carries': 'A bitstring of supported capabilities. Each bit corresponds to a distinct workload (transcoding profile, AI pipeline, BYOC capability)',
},
{
  Field: <Subtitle variant="changelog">**Capability constraints**</Subtitle>,
  'What it carries': 'Per-capability map keyed by model identifier. Each entry records whether the model is warm-loaded, per-model concurrency capacity, runner version',
},
{
  Field: <Subtitle variant="changelog">**PriceInfo**</Subtitle>,
  'What it carries': 'Price-per-unit (price per pixel for transcoding, per-model price for AI). Held in `CapabilityPriceMenu` for AI workloads',
},
{
  Field: <Subtitle variant="changelog">**TicketParams**</Subtitle>,
  'What it carries': '`faceValue`, `winProb`, `recipientRandHash`, `seed`, `expirationBlock`, `expirationParams`. The Gateway uses these to construct valid tickets',
},
{
  Field: <Subtitle variant="changelog">**Address**</Subtitle>,
  'What it carries': 'Orchestrator ETH address; ticket payouts settle to this address on-chain',
},
{
  Field: <Subtitle variant="changelog">**Nodes**</Subtitle>,
  'What it carries': 'Optional `[]string` advertising additional `OrchestratorNode` instances behind the same operator',
},
]}
/>

Capability matching uses `caps.bitstring.CompatibleWith()` defined in `core/capabilities.go`. The check at the Gateway is a simple AND across the bitstrings: the Gateway's required capability bits must be a subset of the Orchestrator's offered bits, and all per-model constraints must be satisfiable.

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

## Session Selection

Selection is the Gateway's decision: of the candidates that returned valid `OrchestratorInfo`, which one to open the session with. The decision combines capability match, price ceiling, latency, and operator reputation.

<DynamicTableV2
  headerList={['Selection criterion', 'How the gateway applies it']}
  itemsList={[
{
  'Selection criterion': <Subtitle variant="changelog">**Capability match**</Subtitle>,
  'How the Gateway applies it': 'Hard filter. An Orchestrator that does not advertise the required capability is not considered',
},
{
  'Selection criterion': <Subtitle variant="changelog">**Price ceiling**</Subtitle>,
  'How the Gateway applies it': 'Hard filter. Gateway has a max price-per-unit for the workload; Orchestrators above the ceiling are dropped',
},
{
  'Selection criterion': <Subtitle variant="changelog">**Latency**</Subtitle>,
  'How the Gateway applies it': 'Soft sort. Sorted ascending by the RPC round-trip measured during discovery',
},
{
  'Selection criterion': <Subtitle variant="changelog">**Reputation**</Subtitle>,
  'How the Gateway applies it': 'Soft penalty. Orchestrators that previously failed jobs are suspended for a cooldown; persistent failures push them down the candidate list',
},
{
  'Selection criterion': <Subtitle variant="changelog">**Stake weight**</Subtitle>,
  'How the Gateway applies it': 'Optional. Some Gateways prefer higher-staked Orchestrators as a reliability proxy; not enforced at the protocol layer',
},
]}
/>

Once selected, the Gateway opens a session by sending the first segment with an attached ticket. The Orchestrator validates the ticket, queues the segment, and processes the work. The session stays open for the duration of the job; for live workloads it stays open until the stream ends.

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

## Job Dispatch

Job dispatch is the per-unit hand-off. Each segment or frame travels with a ticket attached.

The dispatch loop is in `core/orchestrator.go:transcodeSegmentLoop`. Each Orchestrator session has a `SegmentChan` of buffer size `maxSegmentChannels = 4`. The Gateway POSTs each segment with a `TicketSenderParams`; the Orchestrator's `ProcessPayment` validates the ticket via `Recipient.ReceiveTicket`, debits the Gateway's session balance, and routes the segment to either a local transcoder or, if `TranscoderManager` is set, a remote transcoder selected by `selectTranscoder` from the `byLoadFactor`-sorted pool.

<DynamicTableV2
  headerList={['Workload class', 'Transport', 'Cadence', 'Failure recovery']}
  itemsList={[
{
  'Workload class': <Subtitle variant="changelog">**Batch transcoding**</Subtitle>,
  Transport: 'HTTP POST per segment; result returned synchronously',
  Cadence: 'Sub-second for live; throughput-oriented for VOD',
  'Failure recovery': 'Retry up to `maxAttempts`, then `SwapOrchestrator` (re-discover, reselect)',
},
{
  'Workload class': <Subtitle variant="changelog">**Batch AI inference**</Subtitle>,
  Transport: 'HTTP POST with inference payload; result returned synchronously',
  Cadence: 'Seconds to tens of seconds per inference',
  'Failure recovery': 'Same retry-then-swap pattern; cold model load triggers warmup not retry',
},
{
  'Workload class': <Subtitle variant="changelog">**Real-time AI**</Subtitle>,
  Transport: 'Trickle protocol channels (video, audio, control); frames flow continuously',
  Cadence: 'Sub-second per frame',
  'Failure recovery': 'Session re-establishes against next candidate; in-flight frames lost',
},
]}
/>

A single `OrchestratorNode` accepts at most `MaxSessions` concurrent sessions (default 10, raised by `SetMaxSessions` or auto-adjusted by `AutoSessionLimit`). Sessions inactive for `transcodeLoopTimeout` (70 seconds) are torn down.

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

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

## Ticket Plumbing

Tickets are how value moves off-chain. Every dispatched segment carries one. Most are losing tickets that never settle; only winning tickets reach the chain.

A ticket is a signed message with five fields the Gateway sets and one the Orchestrator validates:

<DynamicTableV2
  headerList={['Ticket field', 'Set by', 'Purpose']}
  itemsList={[
{
  'Ticket field': <Subtitle variant="changelog">**`faceValue`**</Subtitle>,
  'Set by': 'Gateway',
  Purpose: 'ETH paid to the Orchestrator on a winning ticket',
},
{
  'Ticket field': <Subtitle variant="changelog">**`winProb`**</Subtitle>,
  'Set by': 'Gateway',
  Purpose: 'Probability the ticket wins. `expectedValue = faceValue × winProb` matches the contracted price for the work',
},
{
  'Ticket field': <Subtitle variant="changelog">**`recipientRandHash`**</Subtitle>,
  'Set by': 'Orchestrator (in advertisement)',
  Purpose: 'Hash committing the Orchestrator to a randomness value; revealed at redemption',
},
{
  'Ticket field': <Subtitle variant="changelog">**`seed`**</Subtitle>,
  'Set by': 'Gateway',
  Purpose: 'Per-ticket randomness contributing to the win check',
},
{
  'Ticket field': <Subtitle variant="changelog">**`senderNonce`**</Subtitle>,
  'Set by': 'Gateway',
  Purpose: 'Monotonic counter preventing ticket replay',
},
{
  'Ticket field': <Subtitle variant="changelog">**`sig`**</Subtitle>,
  'Set by': 'Gateway',
  Purpose: 'Signature over the ticket fields, validated by `Recipient.ReceiveTicket`',
},
]}
/>

The win check combines `seed`, the Orchestrator's `recipientRand`, and the round's stored block hash: `H(sig, recipientRand) < winProb × 2^256`. Neither party can predict winners alone. The Orchestrator queues winning tickets locally and redeems them on-chain in batches when gas conditions are favourable.

<Tip>
  The expected value of any single ticket is `faceValue × winProb`. A Gateway sets the win probability so that per-segment expected payment matches the work being purchased. Orchestrators batch winning tickets and choose redemption timing.
</Tip>

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

## Settlement Triggers

Settlement is when the off-chain layer reaches into the on-chain Protocol. Three triggers cover every on-chain transaction in the per-job loop.

<DynamicTableV2
  headerList={['Trigger', 'Initiated by', 'On-chain effect']}
  itemsList={[
{
  Trigger: <Subtitle variant="changelog">**Gateway funding**</Subtitle>,
  'Initiated by': 'Gateway calls `fundDepositAndReserve()` on `TicketBroker`',
  'On-chain effect': 'ETH moves from Gateway wallet to its `TicketBroker` account; deposit covers active session liquidity, reserve covers redemption if deposit is exhausted',
},
{
  Trigger: <Subtitle variant="changelog">**Winning ticket redemption**</Subtitle>,
  'Initiated by': 'Orchestrator calls `redeemWinningTicket()` on `TicketBroker`',
  'On-chain effect': 'Contract validates ticket against on-chain state, transfers ETH from Gateway deposit to Orchestrator fee pool via `BondingManager`. Emits `WinningTicketRedeemed` and `WinningTicketTransfer`',
},
{
  Trigger: <Subtitle variant="changelog">**Per-round reward**</Subtitle>,
  'Initiated by': 'Active Orchestrator calls `reward()` once per round',
  'On-chain effect': 'Mints round inflation via `Minter`, routes treasury cut, distributes remainder by stake share, accrues Delegator share to claim queue. Emits `Reward`',
},
]}
/>

Three more on-chain events are Gateway-side and run alongside but outside the per-job loop:

* **Gateway exit:** Gateway calls `unlock()` then `withdraw()` on `TicketBroker` to recover unredeemed deposit and reserve
* **Bond changes:** Delegator or Orchestrator calls `bond()`, `unbond()`, `rebond()`, `withdrawStake()` on `BondingManager`
* **Service URI updates:** Orchestrator calls `setServiceURI()` on `ServiceRegistry` when its endpoint changes

For the on-chain mechanics that back each trigger, see <LinkArrow href="/v2/about/protocol/mechanisms" label="Protocol Core Mechanisms" newline={false} />.

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

## Round Heartbeat

Rounds are the on-chain Protocol's clock. Round transitions ripple through the off-chain Network in three ways.

<DynamicTableV2
  headerList={['Round event', 'How the Network reacts']}
  itemsList={[
{
  'Round event': <Subtitle variant="changelog">**Round opens**</Subtitle>,
  'How the Network reacts': 'Active Set locks for the new round. Any Orchestrator that bonded mid-round becomes eligible at this boundary. Active Orchestrators record the new round number locally for reward-call eligibility',
},
{
  'Round event': <Subtitle variant="changelog">**Reward call window**</Subtitle>,
  'How the Network reacts': 'Each active Orchestrator can call `reward()` at most once per round. The off-chain logic schedules this call early in the round to avoid the round-end lock. Missing the call forfeits the round\'s LPT inflation share',
},
{
  'Round event': <Subtitle variant="changelog">**Round-end lock**</Subtitle>,
  'How the Network reacts': 'In the final blocks of a round, parameter updates (`rewardCut`, `feeShare`) and bond changes are blocked on-chain. Off-chain capability advertisement and pricing can still change, but they cannot affect the round in progress',
},
{
  'Round event': <Subtitle variant="changelog">**Round close → next round opens**</Subtitle>,
  'How the Network reacts': 'Anyone (typically an Orchestrator) calls `initializeRound()`. Round number increments; new Active Set is fixed; new mintable rewards are unlocked',
},
]}
/>

The off-chain Network does not stop or slow down at round boundaries. Job dispatch, ticket exchange, and discovery continue. What changes is which Orchestrators are eligible for the next round's reward call and which are now in the Active Set.

<Tip>
  Round length on Arbitrum One is approximately 21 hours (\~5,760 L2 blocks). A given Orchestrator that misses `reward()` in a round forfeits that round's LPT inflation for itself and its Delegators. The off-chain operator software typically schedules the reward call early in each round to avoid the round-end lock.
</Tip>

<CustomDivider />

## Related Pages

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

  <Card title={<CustomCardTitle icon="store" title="Marketplace" />} href="/v2/about/network/marketplace" horizontal arrow>
    Market shape, settlement boundary, Probabilistic Micropayments, trust.
  </Card>

  <Card title={<CustomCardTitle icon="route" title="Job Pipelines" />} href="/v2/about/network/job-pipelines" horizontal arrow>
    Workload classes, lifecycle, state machine, failure modes.
  </Card>

  <Card title={<CustomCardTitle icon="cogs" title="Protocol Core Mechanisms" />} href="/v2/about/protocol/mechanisms" horizontal arrow>
    On-chain mechanics: rounds, staking, rewards, payments, upgrades.
  </Card>
</Columns>
