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

# Hiro League Concepts

> The core concepts behind Hiro League

export const DiagramViewer = ({children, title = "Diagram"}) => {
  const dialogRef = useRef(null);
  const viewportRef = useRef(null);
  const contentRef = useRef(null);
  const [isOpen, setIsOpen] = useState(false);
  const [zoom, setZoom] = useState(1);
  const [pan, setPan] = useState({
    x: 0,
    y: 0
  });
  const zoomRef = useRef(1);
  const panRef = useRef({
    x: 0,
    y: 0
  });
  const dragRef = useRef(null);
  const open = () => {
    setZoom(1);
    setPan({
      x: 0,
      y: 0
    });
    setIsOpen(true);
  };
  const close = () => {
    setZoom(1);
    setPan({
      x: 0,
      y: 0
    });
    setIsOpen(false);
  };
  const resetView = () => {
    fitDiagramToViewport();
    setPan({
      x: 0,
      y: 0
    });
  };
  const setView = (nextZoom, nextPan) => {
    zoomRef.current = nextZoom;
    panRef.current = nextPan;
    setZoom(nextZoom);
    setPan(nextPan);
  };
  const MaximizeIcon = () => <svg aria-hidden="true" className="diagram-viewer__icon" viewBox="0 0 24 24">
      <path d="M8 3H3v5M16 3h5v5M8 21H3v-5M16 21h5v-5" />
    </svg>;
  const ResetIcon = () => <svg aria-hidden="true" className="diagram-viewer__icon" viewBox="0 0 24 24">
      <path d="M3 12a9 9 0 1 0 3-6.7L3 8" />
      <path d="M3 3v5h5" />
    </svg>;
  const CloseIcon = () => <svg aria-hidden="true" className="diagram-viewer__icon" viewBox="0 0 24 24">
      <path d="M18 6 6 18M6 6l12 12" />
    </svg>;
  const fitDiagramToViewport = () => {
    const viewport = viewportRef.current;
    const content = contentRef.current;
    const svg = content?.querySelector(".mermaid svg[viewBox]");
    if (!viewport || !svg) return;
    const viewBox = svg.getAttribute("viewBox")?.split(/\s+/).map(Number);
    if (!viewBox || viewBox.length !== 4 || !viewBox[2] || !viewBox[3]) return;
    const [, , width, height] = viewBox;
    svg.style.width = `${width}px`;
    svg.style.maxWidth = "none";
    const rect = viewport.getBoundingClientRect();
    const nextZoom = Math.min(2.2, Math.max(0.75, Number((Math.min((rect.width - 96) / width, (rect.height - 96) / height) * 0.98).toFixed(3))));
    setView(nextZoom, {
      x: 0,
      y: 0
    });
  };
  const handleWheel = event => {
    if (!isOpen) return;
    event.preventDefault();
    event.stopPropagation();
    const viewport = viewportRef.current;
    if (!viewport) return;
    const rect = viewport.getBoundingClientRect();
    const pointerX = event.clientX - rect.left - rect.width / 2;
    const pointerY = event.clientY - rect.top - rect.height / 2;
    const currentZoom = zoomRef.current;
    const currentPan = panRef.current;
    const nextZoom = Math.max(0.25, Math.min(5, Number((currentZoom * (event.deltaY < 0 ? 1.12 : 0.88)).toFixed(3))));
    const ratio = nextZoom / currentZoom;
    setView(nextZoom, {
      x: pointerX - (pointerX - currentPan.x) * ratio,
      y: pointerY - (pointerY - currentPan.y) * ratio
    });
  };
  const onPointerDown = event => {
    if (!isOpen || event.button !== 0) return;
    event.preventDefault();
    dragRef.current = {
      pointerId: event.pointerId,
      startX: event.clientX,
      startY: event.clientY,
      pan: panRef.current
    };
    try {
      event.currentTarget.setPointerCapture?.(event.pointerId);
    } catch {}
  };
  const onPointerMove = event => {
    const drag = dragRef.current;
    if (!isOpen || !drag || drag.pointerId !== event.pointerId) return;
    event.preventDefault();
    const nextPan = {
      x: drag.pan.x + event.clientX - drag.startX,
      y: drag.pan.y + event.clientY - drag.startY
    };
    panRef.current = nextPan;
    setPan(nextPan);
  };
  const endDrag = event => {
    if (dragRef.current?.pointerId === event.pointerId) {
      dragRef.current = null;
    }
  };
  useEffect(() => {
    const dialog = dialogRef.current;
    if (isOpen && dialog && !dialog.open) {
      dialog.showModal();
      requestAnimationFrame(() => {
        requestAnimationFrame(fitDiagramToViewport);
      });
    }
    if (!isOpen && dialog?.open) {
      dialog.close();
    }
  }, [isOpen]);
  useEffect(() => {
    if (!isOpen) return;
    const onKeyDown = event => {
      if (event.key === "Escape") close();
    };
    const onWheel = event => {
      event.preventDefault();
      event.stopPropagation();
      if (viewportRef.current?.contains(event.target)) {
        handleWheel(event);
      }
    };
    const previousOverflow = document.body.style.overflow;
    const previousHtmlOverflow = document.documentElement.style.overflow;
    document.body.style.overflow = "hidden";
    document.documentElement.style.overflow = "hidden";
    window.addEventListener("keydown", onKeyDown);
    window.addEventListener("wheel", onWheel, {
      capture: true,
      passive: false
    });
    return () => {
      document.body.style.overflow = previousOverflow;
      document.documentElement.style.overflow = previousHtmlOverflow;
      window.removeEventListener("keydown", onKeyDown);
      window.removeEventListener("wheel", onWheel, {
        capture: true
      });
    };
  }, [isOpen]);
  const toolbar = <div className="diagram-viewer__bar">
      <span className="diagram-viewer__title">{title}</span>
      <div className="diagram-viewer__actions">
        {isOpen && <span className="diagram-viewer__zoom" aria-live="polite">
            {Math.round(zoom * 100)}%
          </span>}
        {isOpen && <button className="diagram-viewer__button" type="button" onClick={resetView} aria-label="Reset diagram">
            <ResetIcon />
          </button>}
        <button className="diagram-viewer__button" type="button" onClick={isOpen ? close : open} aria-label={isOpen ? "Close" : "Maximize"}>
          {isOpen ? <CloseIcon /> : <MaximizeIcon />}
        </button>
      </div>
    </div>;
  const diagram = <div ref={viewportRef} className="diagram-viewer__viewport" onPointerDown={onPointerDown} onPointerMove={onPointerMove} onPointerUp={endDrag} onPointerCancel={endDrag}>
      <div className="diagram-viewer__stage" style={{
    transform: isOpen ? `translate(${pan.x}px, ${pan.y}px)` : undefined
  }}>
        <div className="diagram-viewer__centerer">
          <div ref={contentRef} className="diagram-viewer__content" style={{
    transform: isOpen ? `scale(${zoom})` : "none"
  }}>
            {children}
          </div>
        </div>
      </div>
    </div>;
  return <>
      {!isOpen && <div className="diagram-viewer not-prose">
          {toolbar}
          {diagram}
        </div>}

      <dialog ref={dialogRef} className="diagram-viewer-dialog" onWheel={event => {
    if (event.target === event.currentTarget) {
      event.preventDefault();
      event.stopPropagation();
    }
  }} onCancel={event => {
    event.preventDefault();
    close();
  }} onClose={() => setIsOpen(false)}>
        {isOpen && <div className="diagram-viewer diagram-viewer--open not-prose">
            {toolbar}
            {diagram}
          </div>}
      </dialog>
    </>;
};

### Hiro League

Hiro League is a private team of empathetic AI characters. They are active, emotional and loyal. They live in your private environment to serve you, your family and your home.

***

<DiagramViewer title="Hiro League topology">
  ```mermaid actions={false} theme={null}
  ---
  config:
    layout: dagre
  ---
  flowchart LR
   subgraph Nodes["**Nodes**"]
      direction TB
          Mobile["`**Hiro Mobile**<br>iOS / Android`"]
          Web["Hiro Web"]
          Desktop["`**Hiro Desktop**<br>Win / Mac / *nix`"]
    end

   subgraph Instance["Instance"]
          Gateway["Gateway"]
    end

   subgraph HiroGate["**Hiro Gate**"]
      direction TB
        Instance
    end
   subgraph HiroServer["Hiro Server"]
      direction TB
          Workspace
    end
   subgraph Workspace["Workspace"]
      direction LR
          AIAgents["Hiro <br> Agents"]
          ControlRoom["Control<br>Room"]
    end
   subgraph ThirdPartyApps["Third party<br>apps"]
      direction TB
          WhatsApp["WhatsApp"]
          Telegram["Telegram"]
    end
      Mobile -- WS --> Gateway
      Web -- WS --> Gateway
      Desktop -- WS --> Gateway
      Gateway <-- Device Plugin --> Workspace
      HiroServer <-- WP Plugin --> WhatsApp
      HiroServer <-- TG Plugin --> Telegram

      style ThirdPartyApps stroke-dasharray: 5 5,fill:transparent
      style Nodes stroke-dasharray: 5 5,fill:transparent
  ```
</DiagramViewer>

***

## Core concepts

### Root Node

The Root Node is the desktop where you install and setup **HiroLeague**. It is where **Hiro Server** lives and runs. In a local/network only setup, this is also where you install and run **Hiro Gate**, or the gateway. All Nodes(other Hiro devices) connect to **Hiro Server** (on the Root Node) through the Gateway.

### Hiro Server

Hiro Server is the core component of **HiroLeague**. It controls all of the operations of the HiroLeague ecosystem.

* Communicates with Nodes and Third Party Apps through Channel Plugins
* Configure and manage the Workspaces
* Operate the AI Agents

### Workspaces

A workspace is an instance of the HiroLeague ecosystem. Typically, you will have one workspace on the Root Node. Only HiroLeague developers may install multiple workspaces to test alternative configurations.

### Nodes

Any Hiro device that connects to Hiro Server through the gateway is a Node.
Hiro devices are Mobile, web and desktop applications, used to communicate with Hiro Server. They are richer communication and control devices than using third party apps like WhatsApp or Telegram.

### Control Room

The Control Room is a web app, accessible only from the Root Node. It is used to manage all aspects of **Hiro Server**.

### Hiro Gate (Gateway)

Hiro Gate is the authentication and routing boundary between Nodes and Hiro Server. Hiro Gate is designed to work in a local/network only setup, or in a remote setup where it is installed on a cloud VPS. Remote setup facilitates remote access to the HiroLeague ecosystem from the internet.

**Hiro Gate** can host multiple gateway instances on the same machine. Typically, you will have one gateway instance. Only HiroLeague developers may install multiple gateway instances to test alternative configurations.

### Third Party Apps

Third Party Apps are communication apps that connect to Hiro Server through Channel Plugins. Whatsapp and Telegram are the most common examples.

### CLI Commands

* `hiro` is the command-line interface used to run any HiroLeague command.
* `hirogate` is the CLI command to manage and operate the Gateway. HiroLeague package bundles both `hiro` and `hirogate` commands.

***

## What's next

<CardGroup cols={2}>
  <Card title="Hiro Server Components" icon="server" href="/architecture/concepts/hiro-server-components">
    The concurrent components inside a workspace server process.
  </Card>

  <Card title="Channel Manager" icon="plug" href="/architecture/concepts/channel-manager">
    Plugin spawning, subprocess lifecycle, and JSON-RPC communication.
  </Card>

  <Card title="Communication Manager" icon="arrows-left-right" href="/architecture/concepts/communication-manager">
    Message routing, inbound/outbound queues, and permission checks.
  </Card>

  <Card title="Protocol contract" icon="code-branch" href="/architecture/protocol/protocol-contract">
    Shared wire contract for messages, events, requests, gateway envelopes, and device frames.
  </Card>

  <Card title="Agent Manager" icon="robot" href="/architecture/concepts/agent-manager">
    LLM worker, conversation memory, and available tools.
  </Card>

  <Card title="HTTP Server" icon="server" href="/architecture/concepts/http-server">
    Local status, lifecycle, metrics, character profiles, and Tool Registry API.
  </Card>

  <Card title="Gateway instances" icon="shield" href="/hiro/gateway/gateway-instances">
    Local vs VPS gateway configuration and device authentication.
  </Card>

  <Card title="Tools architecture" icon="wrench" href="/architecture/misc/tools-architecture">
    How CLI commands, HTTP endpoints, and agent tools share the same interface.
  </Card>
</CardGroup>
