- 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.
User-facing model
At startup the daemon does five things:- Runs preflight: resolves flags, paths, singleton/process mode, logging, and
MONAD_HOME. - 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. - Builds network state: resolves loopback host, port, TLS, local fallback, and remote-access policy.
- Builds agent-facing services: model router, approval gate, hooks, commands, memory, scheduling, channels, and daemon handlers.
- 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).
- filesystem watchers only invalidate the current snapshot;
ConfigManagerwaits 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.
Startup and reload internals
The entrypoint is intentionally thin:RuntimeKernel owns lifecycle ordering. Each runtime module declares:
idrequires/aftercriticalitystart(ctx, signal)- optional
reload(current, snapshot, ctx, signal) - optional
stop(current, ctx)
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.tsas the composition root for core lifecycle modules. It imports descriptors; it should not contain business logic. - Keep
application/lifecycle.tsas 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()orConfigManager.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.