-
Notifications
You must be signed in to change notification settings - Fork 4
Entry
The entry point is the composition root for the Clio Coder orchestrator. It receives BootOptions, wires seventeen domain bundles into a single process, and returns a BootResult whose exitCode and bootTimeMs record the outcome. The module src/entry/orchestrator.ts is the single file that imports src/engine, src/interactive, src/tools, and every src/domains/* contract. The CLI subcommand src/cli/clio.ts (see Cli) dynamically imports src/entry/orchestrator.ts and calls bootOrchestrator, which is the only entry point that can load the full domain graph.
Three smaller modules under src/entry/ split out concerns that the heavyweight orchestrator would otherwise drag into every boot:
-
boot-options.tsdefines theBootOptionscontract, kept type-only so callers never pay for the orchestrator graph just to read a type. -
extension-reload.tscoordinates the two-domain publication of extension resources and their user-hook registrations (detailed below). -
with-panes.tsis the sole static importer of panes-only code, dynamically imported only when the panes extension resolves to an active rung (detailed below).
| Concern | Module | Key symbols |
|---|---|---|
| Domain wiring, boot phases, headless/ACP/interactive dispatch | src/entry/orchestrator.ts |
bootOrchestrator, BootResult, bannerConfigurationLine, buildBanner, applyHeadlessSettingsOverlay
|
| Boot contract types | src/entry/boot-options.ts |
BootOptions, HeadlessRunDeadline, HeadlessSamplingOverrides
|
| Extension generation coordination | src/entry/extension-reload.ts |
createExtensionReloadCoordinator, ExtensionReloadCoordinator, ExtensionReloadCoordinatorDeps, ExtensionGenerationCommitted
|
| Extension-to-middleware shape adapter | src/entry/extension-hook-sources.ts |
capturedHookSourcesFor |
| Panes activation decision | src/entry/panes-activation.ts |
resolvePanesEnablement, PanesEnablement
|
| Panes composition surface | src/entry/with-panes.ts |
re-exports createMuxDomainModule, createMuxBridge, createPanesRuntime, createWatchPaneController, createYaziBridge
|
| Task memory lifecycle | src/entry/task-memory-lifecycle.ts |
bindTaskMemoryLifecycle, captureTaskMemoryUsage
|
| Plugin reload and notification | src/entry/plugin-reload.ts |
reloadPluginResourcesAndNotify |
flowchart TD
CLI["src/cli/index.ts"] -->|"dispatch"| CLIO["src/cli/clio.ts"]
CLIO -->|"dynamic import"| ORCH["src/entry/orchestrator.ts"]
ORCH -->|"resolvePanesEnablement"| PANES["src/entry/panes-activation.ts"]
ORCH -->|"dynamic import if active"| WITHP["src/entry/with-panes.ts"]
ORCH -->|"loadDomains"| DOMAINS["Domain modules"]
ORCH -->|"createExtensionReloadCoordinator"| EXT["src/entry/extension-reload.ts"]
EXT -->|"capturedHookSourcesFor"| HOOKS["src/entry/extension-hook-sources.ts"]
ORCH -->|"bindTaskMemoryLifecycle"| MEM["src/entry/task-memory-lifecycle.ts"]
ORCH -->|"reloadPluginResourcesAndNotify"| PLUGIN["src/entry/plugin-reload.ts"]
bootOrchestrator(options) in src/entry/orchestrator.ts:1194 sets up a StartupTimer and a shared event bus. When options.terminalLease is present (interactive mode with an instant shell), each boot phase ends with bootPhaseBoundary?.() which calls yieldToEventLoop() and checks the lease's abort signal. This lets the Stage 0 shell answer the terminal (typing, Ctrl+C, resize) while Stage 1 hydrates.
Before domains load, the boot reads project surface files under .clio-coder/safety.yaml and .clio-coder/settings.yaml. Untrusted files are announced on stderr. If the workspace is a git repository, the boot recovers abandoned compete worktrees and task worktrees (recoverCleanupReadyCompeteGroups, recoverTaskWorktrees) so that a dead worker's empty worktree cannot be reused.
The decision to load the panes extension is made before any domain module loads, in src/entry/panes-activation.ts:19. The function resolvePanesEnablement(flag, setting) returns "off", "auto", or "embedded". The CLI flag wins in both directions: --no-panes forces "off"; --with-panes forces "auto". Without a flag, the setting panes.enabled is read, defaulting to "off".
Only when the rung is not "off" and the boot is interactive (!options.headless && !acpMode && CLIO_CODER_INTERACTIVE === "1") does the orchestrator perform await import("./with-panes.js"). This is the dynamic import that keeps panes-only code out of the default boot chunk. The built import graph is pinned by tests/contracts/instant-shell-import-graph.test.ts: the default chunk must carry no mux domain code.
loadDomains receives an array of domain module factories. The order matters: config and extensions come first because later domains resolve through them. The withPanes value, if not null, contributes withPanes.createMuxDomainModule to the list. The loader runs each factory in sequence, calling bootPhaseBoundary between them so that the instant shell can interleave input handling.
After loading, the orchestrator retrieves contracts via result.getContract for dispatch, safety, middleware, session, providers, observability, prompts, agents, resources, extensions, share, mux, context, and interop.
The createExtensionReloadCoordinator in src/entry/extension-reload.ts:127 is the composition root's only writer of both the extensions bundle's snapshot store and the middleware bundle's registration table. It sequences them so that no observer can see extension resources from one generation paired with hooks from another.
The protocol:
- Capture and canonicalize the workspace via
realpathSync. - Call
extensions.prepareReload()to prepare an extension candidate (build, validate, reserve generation). - Build user-hook registrations via
buildUserHookRegistrationsusingcapturedHookSourcesFor(candidate.snapshot)as the bridge between extension and middleware shapes. - Call
middleware.prepareRegistrationReplacement("user-hooks", candidate.generation, registrations)to prepare the middleware replacement. - Validate both prepared states are still current (
candidate.current()andreplacement.current()). - Publish both with adjacent
candidate.publish()andreplacement.publish()calls. - Only after both are live, emit conflicts, the reload event, and issue lines.
The capturedHookSourcesFor function in src/entry/extension-hook-sources.ts:9 adapts the extension snapshot's hookSources array into the middleware-native CapturedHookSourceSet. This is the only place the two domains' shapes meet; middleware never imports extension types.
The coordinator is created with applyBoot() (generation 0 to 1) and reload() (runtime reload). The boot path calls applyBoot() immediately after the coordinator is constructed. The onCommitted callback invokes reloadPluginResourcesAndNotify, which reloads plugin resources and emits a PluginsReloaded bus event.
bindTaskMemoryLifecycle(bus, memory) in src/entry/task-memory-lifecycle.ts:11 subscribes four bus channels:
-
BusChannels.SessionParked→memory.reset() -
BusChannels.SessionResumed→memory.reset() -
BusChannels.SessionTurnSwitched→memory.reset() -
BusChannels.SessionEnd→memory.dispose()
The returned cleanup function calls memory.dispose() and unsubscribes all listeners. The orchestrator calls this immediately after registering the memory intervention hook, and registers the returned cleanup with termination.onDrain.
When withPanes is non-null and mux contract is available, the orchestrator constructs a PanesOperations instance via withPanes.createPanesRuntime. This single instance drives both the panes tool (model-facing) and the /panes slash command (operator-facing), ensuring the model and operator cannot be told different things about the same pane. It also owns the no-mux Yazi chooser.
A concrete trace of how an extension's hook declarations reach a middleware registration during boot:
-
bootOrchestratorcallscreateExtensionReloadCoordinatorwithextensions(the extensions domain contract),middleware(the middleware contract),cwd: () => process.cwd(), and anonCommittedthat callsreloadPlugins. -
extensionReload.applyBoot()is called. - Inside
run(true)inextension-reload.ts:129, the coordinator canonicalizes the workspace, then callsextensions.prepareReload(). - If the extension candidate is prepared,
capturedHookSourcesFor(candidate.snapshot)maps eachhookSources[i]entry to aCapturedHookSourcewith provenance and declarations. -
buildUserHookRegistrationsconsumes this shape and returnsbuilt.registrations. -
middleware.prepareRegistrationReplacement("user-hooks", candidate.generation, built.registrations)returns a prepared replacement. -
candidate.publish()andreplacement.publish()execute adjacently. -
replacement.emitConflicts()fires any conflicts (e.g., duplicate hook IDs). -
deps.onCommitted?.({ generation, previousGeneration, changed, digest })triggersreloadPluginResourcesAndNotify.
Partial publication is impossible by construction. The ExtensionReloadCoordinatorDeps comment states: "Neither publish primitive validates, refuses, throws, or calls out, so a partial publication is impossible by construction: every failure or stale state is detected before step 5 and discards both prepared states." The test tests/extended/extension-reload-coordinator.test.ts verifies this: observePublications wraps both publish primitives with logging, and asserts that the log shows "ext-publish:1" immediately followed by "mw-publish:1" (no interleaving).
Reentrancy guard. The coordinator maintains an inFlight boolean. If applyBoot() or reload() is called while already in flight, it returns a rejected outcome with reason "reentrant".
Workspace canonicalization. The coordinator requires the extension snapshot's cwd to match the canonicalized realpathSync(process.cwd()). A mismatch returns reason: "workspace-changed" and discards the candidate.
Panes exclusion from default chunk. The dynamic import of with-panes.ts is the boundary. The resolvePanesEnablement decision runs before any mux domain code loads, so an inactive boot never resolves a socket path, never performs guest-mode detection, and never pulls the mux domain into the import closure.
-
Adding a new domain: insert its factory in the
loadDomainsarray inbootOrchestrator(after config if it depends on config). The loader will call itsstart()method. -
Adding a panes-dependent feature: statically import it in
src/entry/with-panes.tsand re-export the factory. Do not import panes code fromsrc/entry/orchestrator.ts; the dynamic import boundary is load-bearing. -
Adding a new extension owner: the coordinator hardcodes
"user-hooks"as the middleware owner. A new owner would need a parallel coordinator or a refactored coordinator that accepts the owner ID as a parameter. -
Adding a task-memory event: extend
bindTaskMemoryLifecycleinsrc/entry/task-memory-lifecycle.ts. Currently it only responds toSessionParked,SessionResumed,SessionTurnSwitched, andSessionEnd.
tests/extended/extension-reload-coordinator.test.ts exercises the publication protocol end-to-end. The test keeps boot at generation zero until the composition root publishes the paired first generation installs an extension fixture, constructs a live harness with a real extensions bundle and middleware contract, wraps both publish primitives with logging, and asserts:
- Before
applyBoot():harness.extensions.snapshot()isnull(generation 0),harness.middleware.ownedGeneration("user-hooks")is 0, andextensionSnapshotFor(project)returns the ephemeral path with generation 0. - After
applyBoot(): the log is["ext-publish:1", "mw-publish:1", "committed:1"], proving adjacency. The extensions snapshot shows generation 1, and the middlewareownedGenerationis 1. The hookext-a.hookruns whenturn_startfires, and its receipt carriesextension.generation: 1.
tests/contracts/cli-ignored-flags.test.ts verifies that panes flags are refused by subcommands that cannot honor them. The case refuses --with-panes and names --with-panes and run runs clio-coder --with-panes run hello and asserts exit code 2 with stderr containing --with-panes and clio-coder run.
tests/contracts/panes-tool.test.ts tests the panes tool through the session registry over the real pane runtime. It uses a fake mux that records every request, proving that a refusal reaches no host.
-
The dynamic import of
with-panes.tsis load-bearing. If you statically import mux domain code fromorchestrator.ts, the default boot chunk will include it and the import-graph contract test will fail. The comment insrc/entry/with-panes.ts:5warns: "The built import graph is pinned by tests/contracts/instant-shell-import-graph.test.ts: the default boot chunk must carry no mux domain code." -
applyHeadlessSettingsOverlayclones settings viastructuredClone. It is called at the top of the boot to compute the effective settings for headless runs. It readsoptions.headless.target,model,thinking, andautonomy. Do not mutate the input object; the function expects a clone-safe structure. -
The ExtensionReloadCoordinator's
onCommittedcallback runs after both publications are live. The comment says: "Publication is already complete. An observability callback cannot turn a live paired generation into a thrown or rejected outcome." If your callback throws, the coordinator catches it and logs it as an issue line rather than failing the boot. -
bindTaskMemoryLifecyclereturns a cleanup function that must be called on drain. The orchestrator registers this cleanup withtermination.onDrain. If you add new bus channels to the lifecycle, make sure the returned cleanup unsubscribes them. -
The coordinator's
reportfunction is called for every issue line. In the orchestrator, it writes to stderr for non-interactive runs and pushes toinitialNoticesfor the first boot, then emitsBusChannels.ExtensionsLoadIssuefor subsequent lines. Do not assumereportis only called during boot. -
reloadPluginResourcesAndNotifycompares the previous committed snapshot to the next. It only emits aPluginsReloadedevent if the digest changed. Thechangedfield is computed asprevious === undefined || previous.digest !== next.digest. If you modify plugin resources without changing the digest, the notification will not fire.
Source and generation metadata
title: "Entry point"
summary: "The composition root that wires all domain bundles, resolves boot options, coordinates extension reloads, and activates the panes extension for interactive sessions."
sources:
- "src/entry/orchestrator.ts"
- "src/entry/boot-options.ts"
- "src/entry/extension-reload.ts"
- "src/entry/extension-hook-sources.ts"
- "src/entry/with-panes.ts"
- "src/entry/panes-activation.ts"
- "src/entry/task-memory-lifecycle.ts"
- "src/entry/plugin-reload.ts"
symbols:
- "bootOrchestrator"
- "BootOptions"
- "createExtensionReloadCoordinator"
- "resolvePanesEnablement"
- "bindTaskMemoryLifecycle"
- "reloadPluginResourcesAndNotify"
- "capturedHookSourcesFor"
tests:
- "tests/extended/extension-reload-coordinator.test.ts"
- "tests/contracts/cli-ignored-flags.test.ts"
- "tests/contracts/panes-tool.test.ts"
invariants:
- "Panes-only code never enters the default boot chunk; a plain `clio-coder` run pays zero cost for the mux domain."
- "Extension resources and their user-hook registrations always publish together; no observer sees a generation paired with hooks from a different one."
- "Task memory resets on session park, resume, and turn switch; it disposes on session end."
validate:
- "pnpm run test:file -- tests/contracts/cli-ignored-flags.test.ts"
- "pnpm run test:file -- tests/contracts/panes-tool.test.ts"
- "pnpm run test:file -- tests/extended/extension-reload-coordinator.test.ts"Clio Coder · Repository · Website · Documentation
Wiki v0.1 · Developing implementation reference · Source snapshot: 657dce13d. Authored architecture documents define the product contracts.
- Clio Coder GUI Client
- apps / clio-coder-gui
- Apps clio coder gui server
- Apps clio coder gui tests
- apps
- Architecture
- Command-line surfaces
- Core
- Domains agents
- Config Domain
- Context Domain
- Dispatch domain
- Domains evidence
- Domains extensions
- Domains gateway
- domains
- Domains interop
- Domains lifecycle
- Domains memory
- Middleware Domain
- Domains mux
- Domains observability
- Domains plugins
- Prompt Compiler
- Domains providers
- Domains quota
- Domains resources
- Domains safety
- Domains scheduling
- Domains session
- Vendored Tool Registry and Resolution
- Engine
- Engine acp
- Engine apis
- engine
- Entry point
- Interactive
- interactive
- Interactive overlays
- Interactive renderers
- clio-coder wiki
- Scripts
- Contract tests
- Tests extended
- tests
- Tools
- Tools data
- tools
- Tools verify
- Worker runtime