Skip to content

Latest commit

 

History

History
110 lines (79 loc) · 7.05 KB

File metadata and controls

110 lines (79 loc) · 7.05 KB

Architecture

← Back to main README

A high-level picture of how NAISYS fits together. See each package's README for feature-level detail.

The four packages

Each package ships an npm binary and a programmatic export. The exports are what let one package boot another inside the same process.

Package Binary Entry point Programmatic export
naisys naisys apps/naisys/src/naisys.ts — (top-level process)
@naisys/hub naisys-hub apps/hub/src/naisysHub.ts startHub()
@naisys/supervisor naisys-supervisor apps/supervisor/server/src/supervisorServer.ts supervisorPlugin (Fastify plugin)
@naisys/erp naisys-erp apps/erp/server/src/erpServer.ts erpPlugin (Fastify plugin)
  • naisys — the runner. Proxies a real shell to an LLM, provides ns-* commands (mail, web, images, desktop control), enforces cost and context limits.
  • @naisys/hub — the server. Fastify + Socket.IO. Owns persistence (mail, context logs, cost, variables) so runners stay stateless.
  • @naisys/supervisor — the web UI. Monitors runners and agents, configures models and users, manages permissions.
  • @naisys/erp — optional task system for agents. HATEOAS-driven REST designed for LLMs to self-discover.

Starting each other in the same process

The flags --integrated-hub, --supervisor, and --erp chain one package into the next, ending with every package running in a single Node process on a single port:

  1. The runner parses flags (commander, in naisys.ts). If --integrated-hub is set, it dynamically imports @naisys/hub and calls startHub(...). The await import(moduleName) is deliberate — it avoids a compile-time dependency so the packages can build in parallel, and skips pulling the hub module tree into memory for plain Local runs.
  2. The hub is a Fastify server. If --supervisor was passed through, the hub dynamically imports @naisys/supervisor and registers supervisorPlugin — the supervisor serves its HTTP routes as a Fastify plugin on the hub's port.
  3. If --erp was also set, the supervisor registers erpPlugin the same way. ERP is a transitive dependency of supervisor, not of the runner.
naisys --integrated-hub --supervisor --erp
  └─ await import("@naisys/hub") → startHub()
       └─ await import("@naisys/supervisor") → fastify.register(supervisorPlugin)
             └─ await import("@naisys/erp") → fastify.register(erpPlugin)

Everything runs on one port; the runner then connects back to the in-process hub at http://localhost:<port>/hub as if it were remote.

Runtime topology

             ┌──────────────┐
             │  Supervisor  │   (browser)
             └──────┬───────┘
                    │ WebSocket + REST
                    ▼
   ┌────────┐   ┌───────┐   ┌────────┐
   │ Runner │──▶│  Hub  │◀──│ Runner │   (other machines)
   └────────┘   └───┬───┘   └────────┘
                    │
                    ▼
                Prisma DB

The hub is the only piece that holds state. Runners and supervisors connect in as clients. Any runner can be restarted or moved between machines without losing mail, logs, or cost history. See doc 005 for the multi-machine model and doc 010 for the auth/security model.

Inside the runner

A single runner process can host many agents concurrently.

Agent manager

apps/naisys/src/agent/agentManager.ts owns a runningAgents: AgentRuntime[] list and is the single entry point for start/stop/peek. Only one agent is "active" in the TTY at a time. Inactive agents still run in the background — each has its own OutputService that buffers console writes (capped to 10 lines). setActiveConsoleAgent() swaps the active agent, flushes the buffered lines, and wires stdin to the new one.

Agent runtime

Each agent has its own AgentRuntime — a bag of services scoped to that agent — built by createAgentRuntime() in apps/naisys/src/agent/agentRuntime.ts. Everything per-agent hangs off this object:

  • llmService, costTracker, tools, systemMessage — LLM interaction
  • shellWrapper, workspaces — shell and file context
  • mailService, chatService, mailQueryService — inter-agent messaging
  • commandLoop, commandHandler, commandRegistry, commandProtection — main loop and ns-* dispatch
  • desktopService, computerService, lookService, listenService, genimg, subagentService — feature services (see doc 013 for the desktop/computer-use design)
  • logService, output — logging and buffered console

Factory-function DI

Services are built by factory functions, not classes with constructors. A typical signature:

export function createLLMService(
  { globalConfig }: GlobalConfig,
  { agentConfig }: AgentConfig,
  costTracker: CostTracker,
  tools: CommandTools,
  modelService: ModelService,
  computerService?: ComputerService,
) { ... }

Two techniques keep the graph acyclic:

  1. Closures over getters. Config is passed in as a function (globalConfig() / agentConfig()), not a value. Services read the current value on every call, so config can be reloaded without rebuilding services.
  2. Interface-based late binding. Services that need the agent manager (mail, subagent) depend on the IAgentManager interface at apps/naisys/src/agent/agentManagerInterface.ts, not the concrete class. The manager is the last thing constructed — it closes over the already-built services — and those services call back into it through the interface.

Top-level wiring lives in apps/naisys/src/naisys.ts: services are constructed in dependency order and passed forward, with interfaces substituted wherever a cycle would otherwise form.

Two deployment shapes

  • Integrated — naisys --integrated-hub --supervisor --erp runs every package in a single Node process. This is the "Server" mode in the README.
  • Distributed — a standalone hub with runners on multiple machines, each connecting via --hub=https://.... Agents can run on any host or be pinned to specific ones — useful when a host has unique resources (GPU, desktop, Windows).

Cross-cutting choices

  • Monorepo under apps/, Turbo-repo builds
  • Prisma ORM across the stack; snake_case DB, camelCase code
  • Zod schemas shared between client and server; OpenAPI derived from Zod
  • All packages publish under the @naisys scope on a single version (surfaced on the supervisor admin page)
  • Node >= 22 required