Skip to main content
Monad is a local, daemon-first Agent Team Runtime. The daemon is the only long-lived process that owns team state, identity, policy, credentials, collaboration, extension loading, and network transports. CLI, Web, TUI, Agent Client Protocol, and channel clients enter through public daemon contracts. Monad Mesh is the agent team runtime the daemon exposes. Monad Agent Runtime supplies its first-party model and tool loop; other agent runtimes join the same mesh through adapters while the daemon retains team authority. This document explains the architecture from two angles:
  • Users and operators: what starts, what hot-reloads, and what remains local.
  • Third-party developers: where to extend Monad and which boundaries not to cross.
Scope: inside the process includes the startup graph, lifecycle modules, hot reload, and extension boundaries. The daemon’s outside surface includes binding, physical transports, configuration, environment variables, and the security model. It is runtime.md’s territory. For agent-reachable tool rules, see tools.md.

User-facing model

At startup the daemon does five things:
  1. Runs preflight: resolves flags, paths, singleton/process mode, logging, and MONAD_HOME.
  2. Builds the core runtime: opens the store, loads the four settings files (config.json, agents.json, mesh.json, auth.json), then starts lifecycle modules in dependency order.
  3. Builds network state: resolves loopback host, port, TLS, local fallback, and remote-access policy.
  4. Builds agent-facing services: model router, approval gate, hooks, commands, memory, scheduling, channels, and daemon handlers.
  5. Launches transports: HTTP REST/SSE over TCP and Unix socket, WebSocket push, stdio mode, ACP mode, channels, and background monitors (binding and fallback semantics: runtime.md).
The user-visible result: the daemon becomes reachable, clients can send sessions, and edits to configuration or installed extensions apply without a restart where the owning module supports reload. Hot reload is intentionally conservative. It exists to improve local UX, not to process a high-volume event stream:
  • filesystem watchers only invalidate the current snapshot;
  • ConfigManager waits for a trailing quiet period before applying;
  • only one apply is in flight at a time;
  • if another change arrives while apply is running, the daemon marks itself dirty and applies the final latest snapshot after the active apply settles;
  • accepted state changes only after the owning module commits successfully.
This protects the app from config-write storms and keeps the final user edit from being overwritten by an older in-flight reload.

Startup and reload internals

The entrypoint is intentionally thin:
RuntimeKernel owns lifecycle ordering. Each runtime module declares:
  • id
  • requires / after
  • criticality
  • start(ctx, signal)
  • optional reload(current, snapshot, ctx, signal)
  • optional stop(current, ctx)
The kernel builds topological layers, starts independent modules in a layer concurrently, commits module outputs into RuntimeContext, records serializable lifecycle state in a vanilla Zustand store, reloads modules layer-by-layer, and stops committed modules in reverse dependency order. Current core modules are assembled in runtime/create.ts: ConfigManager is a sibling of RuntimeKernel, not a child. It owns config/auth I/O, watching, invalidation, and accepted snapshots. It delegates schema and file layout to @monad/environment, then calls RuntimeKernel.reload(snapshot) and any application-level reload targets.

Developer extension points

Most third-party work should use one of these public extension surfaces instead of importing daemon internals: Atom and skill authors should not import apps/monad internals. The stable contracts live in @monad/sdk-atom, @monad/protocol, and the documented config files under @monad/environment.

Ownership rules for core contributors

  • Keep lifecycle adapters beside the behavior they own. Do not create a second hierarchy under runtime/modules.
  • Keep runtime/create.ts as the composition root for core lifecycle modules. It imports descriptors; it should not contain business logic.
  • Keep application/lifecycle.ts as orchestration glue for preflight, core, network, agent-facing services, handlers, and transport launch.
  • Do not rebuild the agent loop at daemon startup. Model/provider services start at startup; per-session agent execution is assembled when a session turn runs.
  • Keep runtime objects out of Zustand. Zustand stores serializable lifecycle state only; service instances live in RuntimeContext.
  • Do not introduce RxJS, revision queues, or a global event log for local config reload. Use the existing trailing-debounce, single-flight ConfigManager.
  • Settings writes go through ConfigManager.updateConfig() or ConfigManager.updateAuth() so persistence and hot-apply stay coupled.
  • New reloadable subsystems should expose a stable facade and mutate/reconnect internal handles on reload where possible.

Text sequence