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

# Cloud Runtime

> Run agents, channels, schedules, and triggers around the clock — on Actionist's hosted cloud or your own server.

<div className="mxp-hero xc-border">
  <div className="exp-eyebrow mxp-rise">ALWAYS ON</div>

  <h2 className="mxp-hero-title mxp-rise" style={{animationDelay:'0.06s'}}>
    Your agents run 24/7, even when <span style={{background:'linear-gradient(90deg,#24A4FF,#9000FF,#E3008E)',WebkitBackgroundClip:'text',backgroundClip:'text',color:'transparent'}}>your laptop is closed.</span>
  </h2>

  <p className="mxp-hero-dek mxp-rise" style={{animationDelay:'0.13s'}}>
    Your hosted Actionist runtime. Agents, channels, schedules, and triggers you put in the cloud run here around the clock — even when your computer is off. The same Headless Runtime engine, two ways to host it.
  </p>
</div>

<div className="w-full py-10">
  <div className="actionist-tight flex flex-col gap-2 mb-6">
    <span className="actionist-section-eyebrow">The idea</span>
    <h2 className="text-2xl md:text-3xl font-semibold tracking-tight text-gray-900 dark:text-gray-50">One engine, two places to run it.</h2>

    <p className="text-base text-gray-600 dark:text-gray-400 max-w-3xl">
      Actionist's Headless Runtime is the same engine regardless of where it runs. Choose hosted Cloud for zero infrastructure, or your own VPS for full control.
    </p>
  </div>

  <div className="exp-surface-grid">
    <div className="exp-surface-tile">
      <strong>Hosted Cloud</strong>
      Actionist provisions and manages the container. Your job is reading the status page. Schedules, triggers, Telegram, Slack — all fire when your computer is off. Requires a paid plan.
    </div>

    <div className="exp-surface-tile">
      <strong>Self-hosted VPS</strong>
      Any Linux server with SSH access. You supply the host; Actionist uploads the runtime bundle, writes the env, and starts the process. Full control over region, memory, and browser provider.
    </div>

    <div className="exp-surface-tile">
      <strong>Run Locally</strong>
      The Headless Runtime can also run as a local child process — useful for testing or for a machine that stays on. Schedules and channels stop when the app quits.
    </div>
  </div>

  <Note>
    Both the hosted cloud and self-hosted VPS paths use the same Headless Runtime engine and expose the same capabilities: agents, skills, gateway, channels, schedules, triggers, MCP, marketplace, and more.
  </Note>
</div>

<hr className="xc-divider" />

<div className="w-full py-10 xc-rise">
  <div className="actionist-tight flex flex-col gap-2 mb-6">
    <span className="actionist-section-eyebrow">Hosted cloud</span>
    <h2 className="text-2xl md:text-3xl font-semibold tracking-tight text-gray-900 dark:text-gray-50">The runtime lifecycle, state by state.</h2>

    <p className="text-base text-gray-600 dark:text-gray-400 max-w-3xl">
      Click <strong>Cloud</strong> in the left sidebar (route <code>/cloud-runtime</code>) to see your hosted runtime's current state, then click a state below to read exactly what it means, what the UI shows, and what — if anything — you need to do. The Cloud tab polls every <strong>4 seconds</strong> while provisioning is in progress, then stops once the state is terminal; the runtime heartbeats every <strong>30 seconds</strong> and is considered offline if no ping arrives within <strong>90 seconds</strong>.
    </p>
  </div>

  <div className="xcl-stepper exp-tool-shell xc-try">
    <div className="xcl-chips mxp-srch-chips">
      <label htmlFor="xcl-st-0" className="xcl-chip-0"><input className="mxp-r" type="radio" name="xcl-st" id="xcl-st-0" />No runtime yet</label>
      <label htmlFor="xcl-st-1" className="xcl-chip-1"><input className="mxp-r" type="radio" name="xcl-st" id="xcl-st-1" defaultChecked />Provisioning</label>
      <label htmlFor="xcl-st-2" className="xcl-chip-2"><input className="mxp-r" type="radio" name="xcl-st" id="xcl-st-2" />Starting</label>
      <label htmlFor="xcl-st-3" className="xcl-chip-3"><input className="mxp-r" type="radio" name="xcl-st" id="xcl-st-3" />Active</label>
      <label htmlFor="xcl-st-4" className="xcl-chip-4"><input className="mxp-r" type="radio" name="xcl-st" id="xcl-st-4" />Ready</label>
    </div>

    <div className="xcl-step-row">
      <div className="xcl-step">
        <label htmlFor="xcl-st-0" className="xcl-step-node xcl-node-0">0</label>
        <span className="xcl-step-label xcl-label-0">No runtime</span>
      </div>

      <div className="xcl-step">
        <label htmlFor="xcl-st-1" className="xcl-step-node xcl-node-1">1</label>
        <span className="xcl-step-label xcl-label-1">Provisioning</span>
      </div>

      <div className="xcl-step">
        <label htmlFor="xcl-st-2" className="xcl-step-node xcl-node-2">2</label>
        <span className="xcl-step-label xcl-label-2">Starting</span>
      </div>

      <div className="xcl-step">
        <label htmlFor="xcl-st-3" className="xcl-step-node xcl-node-3">3</label>
        <span className="xcl-step-label xcl-label-3">Active</span>
      </div>

      <div className="xcl-step">
        <label htmlFor="xcl-st-4" className="xcl-step-node xcl-node-4">4</label>
        <span className="xcl-step-label xcl-label-4">Ready</span>
      </div>
    </div>

    <div className="xcl-hb-wrap xcl-hb-row" style={{marginTop:'16px'}}>
      <div className="xcl-hb-dot">
        <div className="xcl-hb-dot-inner" />
      </div>

      <span>Heartbeat · every 30 s · stale after <strong>90 s</strong></span>
    </div>

    <div className="xcl-step-detail xcl-detail-0">
      <span className="xcl-step-detail-title">"No cloud runtime yet"</span>
      Your hosted runtime provisions automatically once your subscription is active. This usually takes a couple of minutes. No action is needed — check back after subscribing. If your plan does not include a cloud runtime, the Cloud tab shows <strong>"Your plan doesn't include a cloud runtime"</strong> with an <strong>Upgrade plan</strong> button. Clicking it opens a Stripe checkout in a new tab.
    </div>

    <div className="xcl-step-detail xcl-detail-1">
      <span className="xcl-step-detail-title">"Setting up your cloud runtime"</span>
      The <code>RuntimeProvisioningStepper</code> steps through four stages: <strong>Provisioning</strong> → <strong>Starting</strong> → <strong>Active</strong> → <strong>Ready</strong>. The page polls every 4 seconds. You can leave this page at any time — setup continues in the background.
    </div>

    <div className="xcl-step-detail xcl-detail-2">
      <span className="xcl-step-detail-title">"Setting up your cloud runtime" (Starting stage)</span>
      The container is launching. Still polling every 4 seconds. No action required. If Starting persists for more than several minutes, contact support.
    </div>

    <div className="xcl-step-detail xcl-detail-3">
      <span className="xcl-step-detail-title">"Your cloud runtime is active"</span>
      The runtime process is up and the Cloud tab is waiting for it to phone home. The runtime sends a heartbeat every 30 seconds; the backend considers it offline if no heartbeat arrives within 90 seconds. If this state persists for more than a few minutes, port 9090 may be blocked or outbound HTTP is firewalled — confirm port 9090 inbound and unrestricted outbound HTTP.
    </div>

    <div className="xcl-step-detail xcl-detail-4">
      <span className="xcl-step-detail-title">"Your cloud runtime is ready"</span>
      Agents and channels assigned to the cloud are running. The <strong>Runtime details</strong> card shows Plan, Region, Version, Browser, and Last heartbeat. The <strong>Connected channels</strong> card shows Telegram and Slack as Connected or Not connected. The runtime is healthy when <code>status === 'active'</code> and the last heartbeat is within the past 90 seconds.
    </div>
  </div>

  <div className="exp-surface-grid" style={{marginTop:'22px'}}>
    <div className="exp-surface-tile">
      <strong>Suspended state.</strong> Update your billing to resume. Your data is retained while suspended.
    </div>

    <div className="exp-surface-tile">
      <strong>Failed state.</strong> Actionist retries automatically — <code>Provisioning</code> → retry. If the state persists, contact support.
    </div>

    <div className="exp-surface-tile">
      <strong>Refresh button.</strong> Forces an immediate status poll at any time. Available in the top-right corner of the Cloud tab.
    </div>
  </div>
</div>

<div className="xcl-mock-wrap xc-rise">
  <div className="mxp-window">
    <div className="mxp-window-bar">
      <span className="mxp-window-dot xcl-mock-dot" />

      <span className="mxp-window-dot xcl-mock-dot" />

      <span className="mxp-window-dot xcl-mock-dot" />

      <span className="mxp-window-title">Cloud Runtime — lifecycle states</span>
    </div>

    <div className="xcl-mock-body xcl-mock-states-grid">
      <div className="xcl-mock-state-panel">
        <div className="xcl-mock-state-label" style={{background:"rgb(144 0 255 / 0.1)",color:"rgb(100 0 180)",border:"1px solid rgb(144 0 255 / 0.25)"}}>Provisioning</div>
        <div className="xcl-mock-row"><span style={{display:"inline-block",width:"8px",height:"8px",borderRadius:"50%",flex:"none",background:"rgb(144 0 255)",boxShadow:"0 0 5px rgb(144 0 255 / 0.5)"}} /><span className="xcl-mock-row-text">Setting up your cloud runtime</span></div>

        <div style={{height:"4px",borderRadius:"99px",background:"rgb(0 0 0 / 0.07)",overflow:"hidden"}}>
          <div style={{height:"100%",borderRadius:"99px",background:"linear-gradient(90deg,#9000FF,#E3008E)",width:'25%'}} />
        </div>

        <div className="xcl-mock-row xcl-mock-row-sm"><span className="xcl-mock-chip" style={{borderColor:"rgb(144 0 255 / 0.4)",color:"rgb(120 0 200)",background:"rgb(144 0 255 / 0.1)"}}>Provisioning</span><span className="xcl-mock-chip">Starting</span><span className="xcl-mock-chip">Active</span><span className="xcl-mock-chip">Ready</span></div>
      </div>

      <div className="xcl-mock-state-panel">
        <div className="xcl-mock-state-label" style={{background:"rgb(255 160 0 / 0.1)",color:"rgb(160 100 0)",border:"1px solid rgb(255 160 0 / 0.3)"}}>Active · awaiting heartbeat</div>
        <div className="xcl-mock-row"><span className="xcl-mock-spinner" /><span className="xcl-mock-row-text">Waiting for runtime to phone home</span></div>
        <div className="xcl-mock-row xcl-mock-row-sm"><span className="xcl-mock-badge" style={{background:"rgb(255 193 7 / 0.1)",color:"rgb(140 90 0)",border:"1px solid rgb(255 193 7 / 0.3)"}}>Active</span><span className="xcl-mock-row-text xcl-muted">Last heartbeat: never</span></div>
        <div className="xcl-mock-row xcl-mock-row-sm xcl-muted">Heartbeat expected every 30 s</div>
      </div>

      <div className="xcl-mock-state-panel">
        <div className="xcl-mock-state-label" style={{background:"rgb(110 227 181 / 0.1)",color:"rgb(0 120 80)",border:"1px solid rgb(110 227 181 / 0.3)"}}>Ready</div>
        <div className="xcl-mock-row"><span className="xcl-mock-badge xcl-badge-green">Ready</span><span className="xcl-mock-row-text">Your cloud runtime is ready</span></div>

        <div style={{padding:"8px 10px",borderRadius:"7px",border:"1px solid rgb(0 0 0 / 0.07)",background:"rgb(250 250 255)",display:"flex",flexDirection:"column",gap:"4px"}}>
          <div className="xcl-mock-mini-row"><span className="xcl-mock-mini-key">Plan</span><span className="xcl-mock-mini-val">Pro</span></div>
          <div className="xcl-mock-mini-row"><span className="xcl-mock-mini-key">Region</span><span className="xcl-mock-mini-val">us-east-1</span></div>
          <div className="xcl-mock-mini-row"><span className="xcl-mock-mini-key">Last heartbeat</span><span className="xcl-mock-mini-val xcl-badge-green-inline">12 s ago</span></div>
        </div>
      </div>

      <div className="xcl-mock-state-panel">
        <div className="xcl-mock-state-label" style={{background:"rgb(255 80 80 / 0.08)",color:"rgb(160 30 30)",border:"1px solid rgb(255 80 80 / 0.25)"}}>Suspended</div>
        <div className="xcl-mock-row"><span className="xcl-mock-badge" style={{background:"rgb(255 80 80 / 0.1)",color:"rgb(160 30 30)",border:"1px solid rgb(255 80 80 / 0.28)"}}>Suspended</span><span className="xcl-mock-row-text">Update your billing to resume</span></div>
        <div className="xcl-mock-row xcl-mock-row-sm xcl-muted">Your data is retained while suspended</div>
        <div style={{display:"flex",marginTop:"4px"}}><span className="xcl-mock-btn-purple">Upgrade plan</span></div>
      </div>
    </div>
  </div>
</div>

<div className="w-full py-10 xc-rise">
  <div className="actionist-tight flex flex-col gap-2 mb-6">
    <span className="actionist-section-eyebrow">Ready state</span>
    <h2 className="text-2xl md:text-3xl font-semibold tracking-tight text-gray-900 dark:text-gray-50">Reading the details and channels cards.</h2>

    <p className="text-base text-gray-600 dark:text-gray-400 max-w-3xl">
      Once the runtime is ready, two cards appear below the status banner.
    </p>
  </div>

  <Columns cols={2}>
    <Card title="Runtime details" icon="info">
      Shows <strong>Plan</strong>, <strong>Region</strong>, <strong>Version</strong>, <strong>Browser</strong>, and <strong>Last heartbeat</strong> timestamp. "Last heartbeat" tells you the last time the runtime phoned home. If it reads "Never", the process has not yet sent its first ping.

      The runtime is considered healthy when `status === 'active'` and the last heartbeat is within the past 90 seconds.
    </Card>

    <Card title="Connected channels" icon="message-square">
      Shows <strong>Telegram</strong> and <strong>Slack</strong> as <strong>Connected</strong> or <strong>Not connected</strong>. This card appears only once the runtime is ready — while provisioning, it shows "Waiting for your cloud runtime to be ready before channels can connect."
    </Card>
  </Columns>

  <Tip>
    The <strong>Refresh</strong> button in the top-right corner of the Cloud tab forces an immediate status poll at any time. Use it if you want confirmation without waiting for the next 4-second cycle.
  </Tip>
</div>

<div className="xcl-mock-wrap xc-rise">
  <div className="mxp-window">
    <div className="mxp-window-bar">
      <span className="mxp-window-dot xcl-mock-dot" />

      <span className="mxp-window-dot xcl-mock-dot" />

      <span className="mxp-window-dot xcl-mock-dot" />

      <span className="mxp-window-title">Cloud Runtime — ready state</span>
      <span className="xcl-mock-btn-sm" style={{marginLeft:"auto"}}>Refresh</span>
    </div>

    <div className="xcl-mock-body">
      <div style={{display:"flex",alignItems:"center",gap:"10px",padding:"10px 14px",borderRadius:"9px",background:"rgb(110 227 181 / 0.08)",border:"1px solid rgb(110 227 181 / 0.2)",marginBottom:"16px",fontSize:"13px",fontWeight:500}}><span className="xcl-mock-badge xcl-badge-green">Ready</span><span>Your cloud runtime is ready</span></div>

      <div className="xcl-mock-two-col">
        <div className="xcl-mock-card">
          <div className="xcl-mock-card-title">Runtime details</div>
          <div className="xcl-mock-mini-row"><span className="xcl-mock-mini-key">Plan</span><span className="xcl-mock-mini-val">Pro</span></div>
          <div className="xcl-mock-mini-row"><span className="xcl-mock-mini-key">Region</span><span className="xcl-mock-mini-val">us-east-1</span></div>
          <div className="xcl-mock-mini-row"><span className="xcl-mock-mini-key">Version</span><span className="xcl-mock-mini-val">2.4.1</span></div>
          <div className="xcl-mock-mini-row"><span className="xcl-mock-mini-key">Browser</span><span className="xcl-mock-mini-val">None</span></div>
          <div className="xcl-mock-mini-row"><span className="xcl-mock-mini-key">Last heartbeat</span><span className="xcl-mock-mini-val xcl-badge-green-inline">8 s ago</span></div>
        </div>

        <div className="xcl-mock-card">
          <div className="xcl-mock-card-title">Connected channels</div>
          <div className="xcl-mock-channel-row"><span className="xcl-mock-channel-icon" style={{background:"rgb(36 164 255 / 0.15)",color:"rgb(20 120 200)",border:"1px solid rgb(36 164 255 / 0.25)"}}>TG</span><span className="xcl-mock-channel-name">Telegram</span><span className="xcl-mock-badge xcl-badge-green">Connected</span></div>
          <div className="xcl-mock-channel-row"><span className="xcl-mock-channel-icon" style={{background:"rgb(230 70 60 / 0.1)",color:"rgb(180 50 40)",border:"1px solid rgb(230 70 60 / 0.2)"}}>SL</span><span className="xcl-mock-channel-name">Slack</span><span className="xcl-mock-badge" style={{background:"rgb(0 0 0 / 0.05)",color:"rgb(80 80 100)",border:"1px solid rgb(0 0 0 / 0.1)"}}>Not connected</span></div>
        </div>
      </div>
    </div>
  </div>
</div>

<hr className="xc-divider" />

<div className="w-full py-10 xc-rise">
  <div className="actionist-tight flex flex-col gap-2 mb-6">
    <span className="actionist-section-eyebrow">Plans</span>
    <h2 className="text-2xl md:text-3xl font-semibold tracking-tight text-gray-900 dark:text-gray-50">The hosted cloud requires a paid plan.</h2>
  </div>

  <Warning>
    If your plan does not include a cloud runtime, the Cloud tab shows <strong>"Your plan doesn't include a cloud runtime"</strong> with an <strong>Upgrade plan</strong> button. Clicking it opens a Stripe checkout in a new tab. Once your subscription is active, the runtime provisions automatically — no further action needed.
  </Warning>

  <Check>
    On a paid plan, the hosted runtime provisions automatically, runs unattended, and restarts itself after a failed state. Your only interaction is the Cloud tab status page and, optionally, the Connected channels card.
  </Check>

  <div className="mt-6">
    <Card title="Upgrade your plan" icon="circle-arrow-up" href="/Folders/billing">
      See what is included in each plan and upgrade directly from the billing page.
    </Card>
  </div>
</div>

<div className="w-full py-10 xc-rise">
  <div className="actionist-tight flex flex-col gap-2 mb-6">
    <span className="actionist-section-eyebrow">Agent Studio</span>
    <h2 className="text-2xl md:text-3xl font-semibold tracking-tight text-gray-900 dark:text-gray-50">Move an agent to VPS in one step.</h2>

    <p className="text-base text-gray-600 dark:text-gray-400 max-w-3xl">
      Once a VPS runtime is paired, you can migrate any locally-running agent — including its memory, skills, schedules, triggers, and channels — in a single click from the Agent Studio. Watch the preflight sequence below.
    </p>
  </div>

  <Warning>
    <strong>Move to VPS is available only in the Desktop app.</strong> The button does not appear in the web interface.
  </Warning>

  <div className="mxp-window" style={{marginTop:'22px'}}>
    <div className="mxp-window-bar">
      <span className="mxp-window-dot" />

      <span className="mxp-window-dot" />

      <span className="mxp-window-dot" />

      <span className="mxp-window-title">Agent Studio · Maya · Move to VPS</span>
    </div>

    <div className="mxp-window-body">
      <div className="mxp-s1" style={{margin:'4px 0 8px'}}><span className="mxp-stream-event">preflight · checking agent, dependencies, channels, memory…</span></div>
      <div className="mxp-stream-line mxp-s2"><span className="mxp-stream-time">check</span><span className="mxp-stream-text"><span className="xcl-check">memory files</span>  3 files · 2 KB</span></div>
      <div className="mxp-stream-line mxp-s3"><span className="mxp-stream-time">check</span><span className="mxp-stream-text"><span className="xcl-check">skills</span>  email-summarise, web-task</span></div>
      <div className="mxp-stream-line mxp-s4"><span className="mxp-stream-time">check</span><span className="mxp-stream-text"><span className="xcl-check">MCP servers</span>  mcp-gmail · mcp-calendar</span></div>
      <div className="mxp-stream-line mxp-s5"><span className="mxp-stream-time">check</span><span className="mxp-stream-text"><span className="xcl-check">schedules</span>  daily-digest · friday-16:00</span></div>
      <div className="mxp-stream-line mxp-s6"><span className="mxp-stream-time">check</span><span className="mxp-stream-text"><span className="xcl-check">triggers</span>  2 active</span></div>
      <div className="mxp-stream-line mxp-s7"><span className="mxp-stream-time">check</span><span className="mxp-stream-text"><span className="xcl-check">channels</span>  Telegram @maya\_bot</span></div>
      <div className="mxp-s8" style={{margin:'8px 0'}}><span className="mxp-stream-event">manifest ready · no blockers</span></div>
      <div className="mxp-stream-line mxp-s9"><span className="mxp-stream-time">ready</span><span className="mxp-stream-text"><span className="xcl-stream-badge local"><i />Local</span> → <span className="xcl-stream-badge cloud"><i />VPS</span>  click <em>Move to VPS</em> to confirm</span></div>
      <div className="mxp-stream-line mxp-s10"><span className="mxp-stream-time">moved</span><span className="mxp-stream-text">Agent <em>Maya</em> is now running on your paired VPS. Schedules, triggers, and channels transferred.<span className="mxp-caret" /></span></div>
    </div>
  </div>

  <Steps>
    <Step title="Open the agent in Agent Studio" icon="user">
      In the desktop app, open <strong>Agents</strong> in the sidebar, then open the agent you want to move.
    </Step>

    <Step title="Click 'Move to VPS'" icon="cloud-upload">
      In the Agent Studio header, click <strong>Move to VPS</strong>. The dialog opens and immediately begins: "Checking agent, dependencies, channels, and memory…"
    </Step>

    <Step title="Review preflight results" icon="list-checks">
      The dialog shows up to three sections:

      * <strong>"Fix before moving"</strong> — blockers that must be resolved before migration can proceed.
      * <strong>"Warnings"</strong> — non-blocking items to review.
      * <strong>"What moves"</strong> — the transfer manifest.

      If there are blockers, fix them in the agent's settings, then click <strong>Re-check</strong>.
    </Step>

    <Step title="Review the transfer manifest" icon="package">
      The <strong>"What moves"</strong> section lists everything that transfers to the VPS: Memory files, Skills, MCP servers, Schedules, Triggers, Channels.
    </Step>

    <Step title="Confirm the move" icon="check">
      With no blockers, the <strong>Move to VPS</strong> button becomes active. Click it. After the move, this agent's chat execution, memory, schedules, triggers, and channels run on your paired VPS.
    </Step>
  </Steps>

  {/* TODO(image): move-to-vps-dialog — IMAGE SPEC: MoveAgentToVpsDialog open in Agent Studio. Show the 'What moves' manifest as a checklist (Memory files, Skills, MCP servers, Schedules, Triggers, Channels — all checked), no blockers visible in the 'Fix before moving' section, and the 'Move to VPS' confirm button active (purple). Agent name visible in the dialog title bar. — replace this CSS mock with the real screenshot */}

  <div className="xcl-mock-wrap" style={{marginTop:'28px'}}>
    <div className="mxp-window">
      <div className="mxp-window-bar">
        <span className="mxp-window-dot xcl-mock-dot" />

        <span className="mxp-window-dot xcl-mock-dot" />

        <span className="mxp-window-dot xcl-mock-dot" />

        <span className="mxp-window-title">Agent Studio · Maya — Move to VPS</span>
      </div>

      <div className="xcl-mock-body" style={{display:"flex",flexDirection:"column",gap:"16px"}}>
        <div style={{flex:1}}>
          <div className="xcl-mock-section-label">What moves</div>

          <div style={{display:"flex",flexDirection:"column",gap:"7px"}}>
            <div className="xcl-mock-check-row"><span className="xcl-mock-check-icon" />Memory files <span className="xcl-muted xcl-mock-meta">3 files · 2 KB</span></div>
            <div className="xcl-mock-check-row"><span className="xcl-mock-check-icon" />Skills <span className="xcl-muted xcl-mock-meta">email-summarise, web-task</span></div>
            <div className="xcl-mock-check-row"><span className="xcl-mock-check-icon" />MCP servers <span className="xcl-muted xcl-mock-meta">mcp-gmail · mcp-calendar</span></div>
            <div className="xcl-mock-check-row"><span className="xcl-mock-check-icon" />Schedules <span className="xcl-muted xcl-mock-meta">daily-digest · friday-16:00</span></div>
            <div className="xcl-mock-check-row"><span className="xcl-mock-check-icon" />Triggers <span className="xcl-muted xcl-mock-meta">2 active</span></div>
            <div className="xcl-mock-check-row"><span className="xcl-mock-check-icon" />Channels <span className="xcl-muted xcl-mock-meta">Telegram @maya\_bot</span></div>
          </div>
        </div>

        <div style={{display:"flex",alignItems:"center",justifyContent:"space-between",gap:"12px",paddingTop:"14px",borderTop:"1px solid rgb(0 0 0 / 0.07)"}}>
          <span className="xcl-mock-badge xcl-badge-green">No blockers</span>
          <span className="xcl-mock-btn-purple" style={{padding:"8px 22px",fontSize:"13px"}}>Move to VPS</span>
        </div>
      </div>
    </div>
  </div>
</div>

<div className="w-full py-10 xc-rise" id="deploy-to-vps">
  <div className="actionist-tight flex flex-col gap-2 mb-6">
    <span className="actionist-section-eyebrow">Self-hosted</span>
    <h2 className="text-2xl md:text-3xl font-semibold tracking-tight text-gray-900 dark:text-gray-50">Deploy the runtime to your own VPS.</h2>

    <p className="text-base text-gray-600 dark:text-gray-400 max-w-3xl">
      Any Linux server with SSH access works. Actionist uploads the runtime bundle via SCP, installs dependencies, writes the environment file, and starts the process.
    </p>
  </div>

  <Steps>
    <Step title="Open Settings → Headless Runtime" icon="settings">
      Go to <strong>Settings</strong> → <strong>Headless Runtime</strong> and select the <strong>Deploy to VPS</strong> mode (CloudArrowUp icon).
    </Step>

    <Step title="Fill in Connection" icon="terminal">
      Enter your server's <strong>Host</strong> (IP or hostname), <strong>Port</strong> (default: 22), <strong>Username</strong>, and <strong>SSH Private Key</strong> (click <strong>Choose…</strong> to pick a file from your machine).
    </Step>

    <Step title="Fill in Authentication" icon="key">
      Enter your <strong>API Key</strong>. Set <strong>Health Port</strong> (default: 9090) and <strong>Files Port</strong> (default: 7783) if your server uses non-standard ports.
    </Step>

    <Step title="Fill in Telegram Gateway" icon="send">
      Enter your <strong>Telegram Bot Token</strong> (required to enable the gateway). Optionally enter <strong>Allowed User IDs</strong> as a comma-separated list to restrict who can message the bot.

      <Warning>
        In production, always set Allowed User IDs. Without it, anyone who discovers the bot's username can send it messages.
      </Warning>
    </Step>

    <Step title="Test the connection" icon="wifi">
      Click <strong>Test Connection</strong> to verify SSH reachability before deploying. A banner reads "SSH connection successful!" on success, or shows an inline error if the key or host is wrong.
    </Step>

    <Step title="Deploy" icon="rocket">
      Click <strong>Deploy to VPS</strong>. If your organization already has an active paired runtime, a confirmation dialog appears:

      > <strong>"Replace existing paired runtime?"</strong>
      > "Your organization already has an active runtime. Deploying here will revoke it. Continue?"

      Click <strong>Replace and deploy</strong> to proceed, or <strong>Cancel</strong> to keep the existing runtime.
    </Step>

    <Step title="Monitor the running runtime" icon="activity">
      Once running, three actions are available:

      * <strong>Check Status</strong> — polls the `/healthz` health endpoint on port 9090.
      * <strong>Logs</strong> — fetches the last 50 lines from `runtime.log`.
      * <strong>Stop</strong> — terminates the remote process.
    </Step>
  </Steps>

  {/* TODO(image): vps-deploy-form — IMAGE SPEC: Settings → Headless Runtime panel with Deploy to VPS mode selected. Show all three form sections (Connection: Host/Port/Username/SSH Private Key fields; Authentication: API Key/Health Port/Files Port; Telegram Gateway: Bot Token/Allowed User IDs). 'Test Connection' and 'Deploy to VPS' buttons visible at the bottom. All fields empty/placeholder values. — replace this CSS mock with the real screenshot */}

  <div className="xcl-mock-wrap" style={{marginTop:'28px'}}>
    <div className="mxp-window">
      <div className="mxp-window-bar">
        <span className="mxp-window-dot xcl-mock-dot" />

        <span className="mxp-window-dot xcl-mock-dot" />

        <span className="mxp-window-dot xcl-mock-dot" />

        <span className="mxp-window-title">Settings · Headless Runtime — Deploy to VPS</span>
      </div>

      <div className="xcl-mock-body" style={{display:"flex",flexDirection:"column",gap:"18px"}}>
        <div className="xcl-mock-form-section">
          <div className="xcl-mock-section-label">Connection</div>
          <div className="xcl-mock-field-row"><span className="xcl-mock-field-label">Host</span><span className="xcl-mock-field-input xcl-muted">192.168.1.1</span></div>
          <div className="xcl-mock-field-row"><span className="xcl-mock-field-label">Port</span><span className="xcl-mock-field-input xcl-muted">22</span></div>
          <div className="xcl-mock-field-row"><span className="xcl-mock-field-label">Username</span><span className="xcl-mock-field-input xcl-muted">root</span></div>
          <div className="xcl-mock-field-row"><span className="xcl-mock-field-label">SSH Private Key</span><span className="xcl-mock-field-input xcl-muted">\~/.ssh/id\_ed25519</span><span className="xcl-mock-btn-sm">Choose…</span></div>
        </div>

        <div className="xcl-mock-form-section">
          <div className="xcl-mock-section-label">Authentication</div>
          <div className="xcl-mock-field-row"><span className="xcl-mock-field-label">API Key</span><span className="xcl-mock-field-input xcl-muted">••••••••</span></div>
          <div className="xcl-mock-field-row"><span className="xcl-mock-field-label">Health Port</span><span className="xcl-mock-field-input xcl-muted">9090</span></div>
          <div className="xcl-mock-field-row"><span className="xcl-mock-field-label">Files Port</span><span className="xcl-mock-field-input xcl-muted">7783</span></div>
        </div>

        <div className="xcl-mock-form-section">
          <div className="xcl-mock-section-label">Telegram Gateway</div>
          <div className="xcl-mock-field-row"><span className="xcl-mock-field-label">Bot Token</span><span className="xcl-mock-field-input xcl-muted">1234567890:AAH…</span></div>
          <div className="xcl-mock-field-row"><span className="xcl-mock-field-label">Allowed User IDs</span><span className="xcl-mock-field-input xcl-muted">123456789, 987654321</span></div>
        </div>

        <div style={{display:"flex",gap:"10px",paddingTop:"4px",justifyContent:"flex-end"}}>
          <span className="xcl-mock-btn-outline">Test Connection</span>
          <span className="xcl-mock-btn-purple">Deploy to VPS</span>
        </div>
      </div>
    </div>
  </div>
</div>

<div className="w-full py-10 xc-rise">
  <div className="actionist-tight flex flex-col gap-2 mb-6">
    <span className="actionist-section-eyebrow">Deploy modes</span>
    <h2 className="text-2xl md:text-3xl font-semibold tracking-tight text-gray-900 dark:text-gray-50">Three ways to run. Pick the right one.</h2>

    <p className="text-base text-gray-600 dark:text-gray-400 max-w-3xl">
      In builds where container deployment is enabled, you can choose how the runtime handles browser automation. Click a mode to see when to use it.
    </p>
  </div>

  <div className="xcl-deploy exp-tool-shell xc-try">
    <input className="mxp-r" type="radio" name="xcl-dm" id="xcl-dm-0" defaultChecked />

    <input className="mxp-r" type="radio" name="xcl-dm" id="xcl-dm-1" />

    <input className="mxp-r" type="radio" name="xcl-dm" id="xcl-dm-2" />

    <div className="xcl-mode-cards">
      <label className="xcl-mode-card xcl-mc-0" htmlFor="xcl-dm-0">
        <div className="xcl-mode-kicker">Hosted Cloud</div>
        <div className="xcl-mode-title">Hosted</div>
        <div className="xcl-mode-dek">Actionist manages the container. Zero infrastructure from you. Requires a paid plan.</div>
        <div className="xcl-mode-ram">Managed · auto-scaled</div>
      </label>

      <label className="xcl-mode-card xcl-mc-1" htmlFor="xcl-dm-1">
        <div className="xcl-mode-kicker">Self-hosted VPS</div>
        <div className="xcl-mode-title">Process mode</div>
        <div className="xcl-mode-dek">One <code>node dist/index.cjs</code> process per server. Simpler default — no container overhead.</div>
        <div className="xcl-mode-ram">\~120 MB idle</div>
      </label>

      <label className="xcl-mode-card xcl-mc-2" htmlFor="xcl-dm-2">
        <div className="xcl-mode-kicker">Advanced VPS</div>
        <div className="xcl-mode-title">Container mode</div>
        <div className="xcl-mode-dek">Isolated container with explicit browser provider selection. Stricter memory isolation.</div>
        <div className="xcl-mode-ram">120 MB – 1.5 GB</div>
      </label>
    </div>

    <div className="xcl-deploy-panel xcl-dp-0">
      <div className="exp-glass-card" style={{padding:'18px 22px'}}>
        <div style={{fontWeight:700,fontSize:'14px',marginBottom:'8px'}}>Hosted Cloud — the zero-ops path</div>

        <p style={{fontSize:'13.5px',lineHeight:'1.65',opacity:0.85,margin:0}}>
          Actionist provisions and manages the container for you. Your job is reading the status page on the Cloud tab. Schedules, triggers, Telegram, and Slack all fire when your computer is off. Auto-retries on failures. Requires an active paid plan — if your plan does not include cloud, the tab shows an Upgrade button.
        </p>
      </div>
    </div>

    <div className="xcl-deploy-panel xcl-dp-1">
      <div className="exp-glass-card" style={{padding:'18px 22px'}}>
        <div style={{fontWeight:700,fontSize:'14px',marginBottom:'8px'}}>Process mode — the simple VPS default</div>

        <p style={{fontSize:'13.5px',lineHeight:'1.65',opacity:0.85,margin:'0 0 12px'}}>
          One <code>node dist/index.cjs</code> process per server. No container, no browser. Use process mode when your agents do not need web browsing — it is the simpler, lighter-weight option that most teams start with.
        </p>

        <div className="exp-surface-grid" style={{marginTop:0}}>
          <div className="exp-surface-tile" style={{padding:'12px 14px'}}>
            <strong>No browser</strong>
            \~120 MB container. Leanest option. Choose when agents do not need web browsing or screenshot-based computer use.
          </div>
        </div>

        <p style={{fontSize:'12.5px',opacity:0.65,margin:'12px 0 0'}}>Use container mode (the third option) when you need strict isolation or explicit browser RAM control.</p>
      </div>
    </div>

    <div className="xcl-deploy-panel xcl-dp-2">
      <div className="exp-glass-card" style={{padding:'18px 22px'}}>
        <div style={{fontWeight:700,fontSize:'14px',marginBottom:'12px'}}>Container mode — browser provider choices</div>

        <div className="exp-surface-grid" style={{marginTop:0}}>
          <div className="exp-surface-tile" style={{padding:'12px 14px'}}>
            <strong>None (\~120 MB)</strong>
            No browser. Leanest container. Choose this when agents do not need web browsing or screenshot-based computer use.
          </div>

          <div className="exp-surface-tile" style={{padding:'12px 14px'}}>
            <strong>Local Chrome (\~1.5 GB)</strong>
            Adds Chrome to the container for web tasks and screenshots. Idle stays near 120 MB; Chrome runs only during a task. Choose when you need browser automation and have sufficient RAM.
          </div>

          <div className="exp-surface-tile" style={{padding:'12px 14px'}}>
            <strong>Browserbase (cloud)</strong>
            Runs the browser off-box on Browserbase's cloud. Container stays near \~1 GB. Browser tasks are metered and billed by Browserbase separately. Best for RAM-constrained VPS or low-traffic agents.
          </div>
        </div>

        <Note>
          Enable container mode by checking <strong>Deploy as isolated container (multi-org)</strong> in the VPS form. Available in builds where container deployment is enabled.
        </Note>
      </div>
    </div>
  </div>
</div>

<hr className="xc-divider" />

<div className="w-full py-10 xc-rise">
  <div className="actionist-tight flex flex-col gap-2 mb-6">
    <span className="actionist-section-eyebrow">Credentials Vault</span>
    <h2 className="text-2xl md:text-3xl font-semibold tracking-tight text-gray-900 dark:text-gray-50">Machine identities and pairing codes.</h2>

    <p className="text-base text-gray-600 dark:text-gray-400 max-w-3xl">
      Every runtime authenticates to the Actionist backend using a long-lived machine bearer token (<code>mid\_\*</code>). The first boot of a VPS runtime exchanges a one-time pairing code for this token and stores it at <code>config/machine-token.json</code>.
    </p>
  </div>

  <div className="mxp-pipe">
    <div className="mxp-pipe-node"><strong>Issue code</strong><span>Settings → Credentials Vault → Pair runtime. One-time code generated.</span></div>

    <div className="mxp-pipe-link" />

    <div className="mxp-pipe-node"><strong>Copy immediately</strong><span>Single-use. Not shown again. If missed, generate a new one.</span></div>

    <div className="mxp-pipe-link d2" />

    <div className="mxp-pipe-node"><strong>First boot</strong><span>Pass as <code>ACTIONIST\_PAIRING\_CODE</code> env var. Runtime exchanges it for a <code>mid\_\*</code> token.</span></div>

    <div className="mxp-pipe-link d3" />

    <div className="mxp-pipe-node"><strong>Token stored</strong><span>Saved to <code>config/machine-token.json</code> locally. Revoke via the Vault panel at any time.</span></div>
  </div>

  <div className="exp-tool-shell" style={{padding:'20px 24px',marginTop:'8px'}}>
    <div style={{fontSize:'12px',letterSpacing:'0.07em',textTransform:'uppercase',opacity:0.55,marginBottom:'14px',fontFamily:'"JetBrains Mono",monospace'}}>Example pairing code (one-time)</div>

    <div style={{display:'flex',alignItems:'center',gap:'16px',flexWrap:'wrap'}}>
      <span className="xcl-pair-code">ACTX-7F2A-K9R3</span>
      <span style={{fontSize:'13px',opacity:0.65}}>Copy immediately — disappears after first use.</span>
    </div>

    <div style={{marginTop:'16px',display:'flex',gap:'10px',alignItems:'center',flexWrap:'wrap'}}>
      <span style={{fontSize:'12.5px',opacity:0.65}}>After exchange:</span>
      <span className="xcl-pair-used">ACTX-7F2A-K9R3</span>
      <span style={{fontSize:'12.5px',opacity:0.55}}>→ <code>mid\_a7f2...k9r3</code> stored at <code>config/machine-token.json</code></span>
    </div>
  </div>

  <Steps>
    <Step title="Open Machine Identities" icon="shield">
      Go to <strong>Settings</strong> → <strong>Credentials Vault</strong> → scroll to the <strong>Paired runtimes</strong> section. You will see all active <code>mid\_\*</code> machine identities and their live health state.
    </Step>

    <Step title="Generate a pairing code" icon="hash">
      Click <strong>Pair runtime</strong>, enter a label (for example, "VPS — Telegram bot"), then click <strong>Issue pairing code</strong>. A one-time code appears.

      <Warning>
        Copy the code immediately. It is single-use and will not be shown again. If you miss it, generate a new one — the old code is already expired.
      </Warning>
    </Step>

    <Step title="Use the code on first boot" icon="terminal">
      Pass the code to the VPS runtime as the <code>ACTIONIST\_PAIRING\_CODE</code> environment variable on first boot. The runtime exchanges it for a <code>mid\_\*</code> token and stores it locally.
    </Step>

    <Step title="Revoke if needed" icon="circle-x">
      To remove a machine identity, click <strong>Revoke</strong> next to it. Access is invalidated immediately — the VPS does not need to be online.
    </Step>
  </Steps>

  {/* TODO(image): machine-identities — IMAGE SPEC: Settings → Credentials Vault → Paired runtimes panel. One active mid_* identity listed with a green health badge showing 'Active', its label 'VPS — Telegram bot', and creation date. 'Pair runtime' button (top-right of panel) and 'Revoke' button (inline next to the identity) both visible. — replace this CSS mock with the real screenshot */}

  <div className="xcl-mock-wrap" style={{marginTop:'28px'}}>
    <div className="mxp-window">
      <div className="mxp-window-bar">
        <span className="mxp-window-dot xcl-mock-dot" />

        <span className="mxp-window-dot xcl-mock-dot" />

        <span className="mxp-window-dot xcl-mock-dot" />

        <span className="mxp-window-title">Settings · Credentials Vault — Paired runtimes</span>
        <span className="xcl-mock-btn-sm" style={{marginLeft:"auto",background:"linear-gradient(90deg,#9000FF,#C800B3)",color:"#fff",border:"none",fontWeight:600}}>Pair runtime</span>
      </div>

      <div className="xcl-mock-body">
        <div style={{display:"flex",alignItems:"center",justifyContent:"space-between",gap:"12px",padding:"12px 14px",borderRadius:"9px",border:"1px solid rgb(0 0 0 / 0.07)",background:"rgb(255 255 255)"}}>
          <div style={{display:"flex",alignItems:"center",gap:"10px",flexWrap:"wrap",fontSize:"12.5px"}}>
            <span className="xcl-mock-badge xcl-badge-green">Active</span>
            <span style={{fontWeight:600,fontSize:"13px"}}>VPS — Telegram bot</span>
            <span className="xcl-muted xcl-mock-meta">mid\_a7f2k9r3… · created Jun 10, 2026</span>
          </div>

          <span className="xcl-mock-btn-outline xcl-mock-btn-sm">Revoke</span>
        </div>
      </div>
    </div>
  </div>

  <Warning>
    If your replace-existing-runtime confirmation dialog appears when deploying, it lists the current active identities. Confirm you are targeting the right server before clicking <strong>Replace and deploy</strong> — revoking the wrong identity cuts that server off immediately.
  </Warning>
</div>

<div className="w-full py-10 xc-rise">
  <div className="actionist-tight flex flex-col gap-2 mb-6">
    <span className="actionist-section-eyebrow">Local mode</span>
    <h2 className="text-2xl md:text-3xl font-semibold tracking-tight text-gray-900 dark:text-gray-50">Run the runtime on your desktop.</h2>

    <p className="text-base text-gray-600 dark:text-gray-400 max-w-3xl">
      If you want Telegram bot connectivity without a remote server, the Headless Runtime can also run as a local child process — useful for testing or for machines that stay on.
    </p>
  </div>

  <Steps>
    <Step title="Select Run Locally" icon="monitor">
      Go to <strong>Settings</strong> → <strong>Headless Runtime</strong> and select <strong>Run Locally</strong> (Desktop icon).
    </Step>

    <Step title="Enter your Telegram Bot Token" icon="send">
      Enter your <strong>Telegram Bot Token</strong> (required). Optionally add <strong>Allowed User IDs</strong>.
    </Step>

    <Step title="Start the runtime" icon="play">
      Click <strong>Start Runtime</strong>. The badge cycles: Stopped → Starting… → Running (shows the process PID). Once connected, the Telegram logo appears with "Connected".
    </Step>

    <Step title="Stop when done" icon="square">
      Click <strong>Stop Runtime</strong> to kill the child process. Scheduled jobs and channels stop with it.
    </Step>
  </Steps>

  <Note>
    Run Locally keeps everything on your machine — it is not a replacement for hosted Cloud or a remote VPS. Schedules and channels stop running when you quit the app or click Stop Runtime.
  </Note>
</div>

<div className="w-full py-10 xc-rise">
  <div className="actionist-tight flex flex-col gap-2 mb-6">
    <span className="actionist-section-eyebrow">Always on</span>
    <h2 className="text-2xl md:text-3xl font-semibold tracking-tight text-gray-900 dark:text-gray-50">While your laptop was closed.</h2>

    <p className="text-base text-gray-600 dark:text-gray-400 max-w-3xl">
      The VPS runtime keeps a rolling log of everything it handled while you were away. Three things happened overnight — a scheduled digest fired, a webhook trigger came in, and a Telegram message got a reply — all without you.
    </p>
  </div>

  <div className="mxp-window" style={{marginTop:'8px'}}>
    <div className="mxp-window-bar">
      <span className="mxp-window-dot" />

      <span className="mxp-window-dot" />

      <span className="mxp-window-dot" />

      <span className="mxp-window-title">VPS Runtime · overnight log · Jun 11–12 2026</span>
    </div>

    <div className="mxp-window-body">
      <div className="mxp-s1" style={{margin:'4px 0 8px'}}><span className="mxp-stream-event">schedule · daily-digest · fired 02:00 UTC</span></div>
      <div className="mxp-stream-line mxp-s2"><span className="mxp-stream-time">02:00</span><span className="mxp-stream-text">agent <em>Maya</em> · schedule <em>daily-digest</em> started</span></div>
      <div className="mxp-stream-line mxp-s3"><span className="mxp-stream-time">02:01</span><span className="mxp-stream-text"><span className="xcl-check">read inbox</span>  23 emails · 4 flagged · summary drafted</span></div>
      <div className="mxp-stream-line mxp-s4"><span className="mxp-stream-time">02:02</span><span className="mxp-stream-text"><span className="xcl-check">sent digest</span>  Telegram · 1 message · delivered</span></div>
      <div className="mxp-stream-line mxp-s5"><span className="mxp-stream-time">02:02</span><span className="mxp-stream-text"><span className="xcl-check">schedule complete</span>  0 errors · next run 02:00 tomorrow</span></div>
      <div className="mxp-s6" style={{margin:'10px 0 8px'}}><span className="mxp-stream-event">trigger · webhook · new-lead · received 04:37 UTC</span></div>
      <div className="mxp-stream-line mxp-s7"><span className="mxp-stream-time">04:37</span><span className="mxp-stream-text">agent <em>Dario</em> · trigger <em>new-lead</em> matched · 1 payload</span></div>
      <div className="mxp-stream-line mxp-s8"><span className="mxp-stream-time">04:38</span><span className="mxp-stream-text"><span className="xcl-check">enriched lead</span>  Northwind Supplies · added to CRM</span></div>
      <div className="mxp-stream-line mxp-s9"><span className="mxp-stream-time">04:38</span><span className="mxp-stream-text"><span className="xcl-check">sent follow-up</span>  personalised email · 1 recipient · delivered</span></div>
      <div className="mxp-s10" style={{margin:'10px 0 8px'}}><span className="mxp-stream-event">channel · Telegram · inbound message · 07:14 UTC</span></div>
      <div className="mxp-stream-line mxp-s1" style={{animationDelay:'1.8s'}}><span className="mxp-stream-time">07:14</span><span className="mxp-stream-text">agent <em>Maya</em> · channel message received · @maya\_actionist\_bot</span></div>
      <div className="mxp-stream-line mxp-s2" style={{animationDelay:'2.0s'}}><span className="mxp-stream-time">07:14</span><span className="mxp-stream-text"><span className="xcl-check">replied</span>  "Tuesday 10 AM is free — want me to block it?" · delivered<span className="mxp-caret" /></span></div>
    </div>
  </div>

  <div className="xc-quote" style={{marginTop:'32px'}}>
    <p>The runtime does not wait for you to wake up. Schedules fire, triggers respond, channels reply — the lid being closed is not a blocker.</p>
    <span>Overnight log · VPS runtime · 3 events handled · 0 errors</span>
  </div>
</div>

<div className="w-full py-10 xc-rise">
  <div className="actionist-tight flex flex-col gap-2 mb-6">
    <span className="actionist-section-eyebrow">Best practices</span>
    <h2 className="text-2xl md:text-3xl font-semibold tracking-tight text-gray-900 dark:text-gray-50">Tips for a reliable runtime.</h2>
  </div>

  <AccordionGroup>
    <Accordion title="'Active' is not the same as 'Ready'" icon="circle-alert">
      The Cloud tab stepper reaches <strong>Ready</strong> only when <code>status === 'active'</code> AND the last heartbeat is within 90 seconds. For a self-hosted VPS runtime: if stuck at "Active" for more than a minute, port 9090 is likely blocked inbound (the health endpoint) or outbound HTTP from the VPS to the Actionist API is firewalled — open port 9090 inbound and confirm outbound HTTP is unrestricted. For a hosted cloud runtime stuck at "Active", contact support.
    </Accordion>

    <Accordion title="Always restrict Telegram to known user IDs in production" icon="shield-check">
      Without Allowed User IDs set, anyone who discovers the bot's username can message it. Set the <strong>Allowed User IDs</strong> field in the VPS form (or the <code>TELEGRAM\_ALLOWED\_IDS</code> environment variable). A comma-separated list of Telegram numeric user IDs is sufficient.
    </Accordion>

    <Accordion title="Test Connection before deploying" icon="wifi">
      Always click <strong>Test Connection</strong> before hitting <strong>Deploy to VPS</strong> to confirm SSH reachability. This surfaces key-path and permission errors before the deployment attempt, saving you a failed deploy cycle.
    </Accordion>

    <Accordion title="Pairing codes are single-use and shown once" icon="hash">
      Generate the pairing code just before you need it. If you close the dialog without copying the code, generate a new one — the old code is already consumed. Each code becomes invalid immediately on first use.
    </Accordion>

    <Accordion title="Use Browserbase for RAM-constrained servers" icon="cpu">
      Local Chrome raises the container memory ceiling to approximately 1.5 GB. Browserbase keeps the container near 1 GB by offloading browser work off-box. For low-traffic agents or VPS instances with under 2 GB of RAM, Browserbase is the safer choice — just account for per-task metered billing.
    </Accordion>

    <Accordion title="Enable vector memory search" icon="brain">
      On VPS, vector-backed memory search can be enabled with additional configuration. Without it, memory search uses full-text only, which reduces the quality of memory recall for agents with large memory stores.
    </Accordion>

    <Accordion title="File proxy requires HTTPS in production" icon="lock">
      To enable web-based file browsing from the VPS, fill in both <strong>Public base URL</strong> and <strong>File proxy secret</strong>. The base URL must be HTTPS — the backend rejects plain HTTP outside of local development. The secret must match <code>ACTIONIST\_VPS\_FILE\_PROXY\_SECRET</code> on the backend.
    </Accordion>

    <Accordion title="Revoke compromised machine identities immediately" icon="circle-x">
      Go to <strong>Settings</strong> → <strong>Credentials Vault</strong> → <strong>Paired runtimes</strong> → <strong>Revoke</strong>. Revocation is immediate and does not require the VPS to be online. The <code>mid\_\*</code> token is invalidated at the backend; the next request from the runtime will fail authentication.
    </Accordion>

    <Accordion title="Schedule failures retry automatically" icon="refresh-cw">
      Failed schedule runs on VPS retry up to 3 times. The delays follow an exponential backoff: 30 s → 60 s → 120 s → capped at 300 s. Execution rows older than 7 days are pruned automatically. If you notice missed runs, check the <strong>Schedule</strong> tab for the run history.
    </Accordion>

    <Accordion title="Leave the provisioning page freely" icon="external-link">
      Once provisioning starts, setup continues in the background. The page itself says "You can leave this page — setup continues in the background." Come back when you want to check progress.
    </Accordion>
  </AccordionGroup>
</div>

<div className="w-full py-10 xc-rise">
  <div className="actionist-tight flex flex-col gap-2 mb-6">
    <span className="actionist-section-eyebrow">Reference</span>
    <h2 className="text-2xl md:text-3xl font-semibold tracking-tight text-gray-900 dark:text-gray-50">Defaults and limits.</h2>
  </div>

  <div className="mxs-limits">
    <div className="mxs-limit"><b className="xc-count" style={{"--xc-to":"4"}} data-suffix=" s"><span className="xc-real">4 s</span></b><span>Cloud tab poll interval (settling)</span></div>
    <div className="mxs-limit"><b className="xc-count" style={{"--xc-to":"30"}} data-suffix=" s"><span className="xc-real">30 s</span></b><span>Heartbeat send interval</span></div>
    <div className="mxs-limit"><b className="xc-count" style={{"--xc-to":"90"}} data-suffix=" s"><span className="xc-real">90 s</span></b><span>Heartbeat freshness threshold</span></div>
    <div className="mxs-limit"><b className="xc-count" style={{"--xc-to":"22"}} data-suffix=""><span className="xc-real">22</span></b><span>Default SSH port</span></div>
    <div className="mxs-limit"><b className="xc-count" style={{"--xc-to":"9090"}} data-suffix=""><span className="xc-real">9090</span></b><span>Health port (healthz endpoint)</span></div>
    <div className="mxs-limit"><b className="xc-count" style={{"--xc-to":"7783"}} data-suffix=""><span className="xc-real">7783</span></b><span>Files port</span></div>
    <div className="mxs-limit"><b className="xc-count" style={{"--xc-to":"50"}} data-suffix=" lines"><span className="xc-real">50 lines</span></b><span>Logs fetched per call</span></div>
    <div className="mxs-limit"><b className="xc-count" style={{"--xc-to":"5"}} data-suffix=" GB"><span className="xc-real">5 GB</span></b><span>Max workspace size</span></div>
    <div className="mxs-limit"><b className="xc-count" style={{"--xc-to":"50"}} data-suffix=" GB"><span className="xc-real">50 GB</span></b><span>Max org size</span></div>
    <div className="mxs-limit"><b className="xc-count" style={{"--xc-to":"30"}} data-suffix=" days"><span className="xc-real">30 days</span></b><span>Artifact retention</span></div>
    <div className="mxs-limit"><b className="xc-count" style={{"--xc-to":"3"}} data-suffix=""><span className="xc-real">3</span></b><span>Schedule retry attempts</span></div>
    <div className="mxs-limit"><b className="xc-count" style={{"--xc-to":"7"}} data-suffix=" days"><span className="xc-real">7 days</span></b><span>Schedule execution row retention</span></div>
    <div className="mxs-limit"><b>\~120 MB</b><span>Browser: None (container RAM)</span></div>
    <div className="mxs-limit"><b>\~1.5 GB</b><span>Browser: Local Chrome (container RAM ceiling)</span></div>
    <div className="mxs-limit"><b>\~1 GB</b><span>Browser: Browserbase (container RAM)</span></div>
  </div>

  <div className="mt-8" />

  | Setting                                       | Default value    |
  | --------------------------------------------- | ---------------- |
  | Cloud tab poll interval (settling)            | 4 seconds        |
  | Heartbeat freshness threshold                 | 90 seconds       |
  | Heartbeat send interval                       | every 30 seconds |
  | SSH port                                      | 22               |
  | Health port                                   | 9090             |
  | Files port                                    | 7783             |
  | Logs fetched                                  | last 50 lines    |
  | Max workspace size                            | 5 GB             |
  | Max org size                                  | 50 GB            |
  | Artifact retention                            | 30 days          |
  | Memory flush interval                         | 30 seconds       |
  | Schedule retry attempts                       | 3                |
  | Schedule retry initial delay                  | 30 seconds       |
  | Schedule retry max delay                      | 300 seconds      |
  | Schedule execution row retention              | 7 days           |
  | Browser: None (container RAM)                 | \~120 MB         |
  | Browser: Local Chrome (container RAM ceiling) | \~1.5 GB         |
  | Browser: Browserbase (container RAM)          | \~1 GB           |
</div>

<div className="w-full py-10 xc-rise">
  <div className="actionist-cta-panel xc-border">
    <div className="relative z-10 flex flex-col items-center gap-5 max-w-2xl mx-auto">
      <span className="actionist-eyebrow">Always on</span>

      <h2 className="text-2xl md:text-3xl font-semibold tracking-tight text-white leading-tight">
        Your agents shouldn't need you to be awake.
      </h2>

      <p className="text-base md:text-lg text-white/85">
        Upgrade to a paid plan and your hosted runtime provisions automatically. Agents, schedules, and channels run around the clock — without your laptop.
      </p>

      <div className="flex flex-wrap items-center justify-center gap-3 pt-2">
        <a className="actionist-cta-btn-primary" href="https://app.actionist.ai" target="_blank" rel="noreferrer">
          Open the Dashboard

          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
            <path d="M5 12h14" />

            <path d="m12 5 7 7-7 7" />
          </svg>
        </a>

        <a className="actionist-cta-btn-secondary" href="/Folders/billing">
          View plans
        </a>
      </div>
    </div>
  </div>
</div>

<div className="w-full pt-2 pb-6">
  <p className="actionist-trust-line">
    Hosted or self-hosted — the same runtime engine, the same capabilities.<br />
    Heartbeats every 30 s · auto-retries on failure · 90 s freshness threshold.
  </p>
</div>

<div className="w-full pb-16 xc-rise">
  <div className="actionist-tight flex flex-col gap-2 mb-6">
    <span className="actionist-section-eyebrow">Keep going</span>
    <h2 className="text-2xl md:text-3xl font-semibold tracking-tight text-gray-900 dark:text-gray-50">Next steps.</h2>
  </div>

  <Columns cols={4}>
    <Card title="Agents" icon="users" href="/Folders/agents">
      Open an agent in the studio and use Move to VPS to migrate it to your paired runtime.
    </Card>

    <Card title="Channels" icon="messages-square" href="/Folders/channels">
      Connect Telegram and Slack bots so agents respond where your team already is — even when your computer is off.
    </Card>

    <Card title="Schedules" icon="calendar-clock" href="/Folders/schedules">
      Set up recurring jobs that fire headlessly on your cloud or VPS runtime.
    </Card>

    <Card title="Billing" icon="credit-card" href="/Folders/billing">
      Upgrade to a paid plan to provision your hosted cloud runtime.
    </Card>
  </Columns>
</div>
