daemon-runtime
SkillMonitoring & opsUse when changing daemon startup, singleton ownership, shutdown, logging, event subscriptions, or lifecycle commands.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the daemon-runtime skill
What this skill tells your AI
The instructions your AI receives, as published by kunchenguid/no-mistakes in .agents/skills/daemon-runtime/SKILL.md and read by ahel’s review.
Daemon Singleton Lock (internal/daemon/lock.go)
- Only one live daemon may own an
NM_HOME: an exclusive OS file lock on<NM_HOME>/daemon.lockis acquired as the very first action inRunWithOptions, strictly before stale-run recovery and socket bind, and held for the process lifetime. The kernel releases it on any process death, so a held lock always means a live holder and no staleness heuristic is needed. Without it, a second daemon stole the socket and ran global crash recovery against the live daemon's runs and worktrees. - Process launch is not readiness: the PID record is published after the singleton lock and before exclusive recovery, while startup succeeds only after a real IPC health response. The 45s production budget covers cold environment setup and recovery; early exits fail promptly, timeout cleanup reaps detached children before fallback or rollback, and managed plus detached failures retain both causes. Regressions:
TestStartDetachedDaemonDetectsChildExitPromptly,TestStartDetachedDaemonTimeoutKillsAndReapsChild,TestStartPreservesManagedAndDetachedFallbackErrors,TestColdDetachedStartupProductionGateCardinality. - A successful stop means the daemon process is gone, not merely that IPC health has disappeared, because only process exit releases the singleton lock. Capture the daemon instance before requesting shutdown, and close the shutdown client before waiting because the daemon drains in-flight handlers during exit. See
waitForDaemonStopandstopDetachedDaemon; regressions: e2eTestDaemonStopLeavesNoDaemonProcessOwningTheRoot,TestDaemonRestartReplacesTheDaemonWithExactlyOneOwner. - Independent layers:
internal/ipclisten()dials the socket before unlinking it and refuses to steal a live one; client probes bound the dial withdaemon_connect_timeoutand fail fast on a dead or wedged socket instead of starting a replacement daemon (EnsureDaemonsurfaces the error with adaemon startrecovery hint; the health RPC itself is bounded separately byipc.DefaultDialTimeout). - Daemon execution is explicit-only (
no-mistakes daemon run --root); never let inherited environment reinterpret probes like--versionorstatusas daemon workers. - Startup worktree cleanup is DB-aware: never remove a worktree whose run row is
pendingorrunning;startRuninserts the run row before creating the worktree, so a no-row directory is safe to remove immediately. That no-row rule holds only inside<NM_HOME>/worktrees, which is discovered by walking because no-mistakes owns it; a configured worktree root is the operator's directory, so cleanup and eject there act on exactly the recorded run worktrees and never enumerate anything else. - The user-facing model lives in
docs/src/content/docs/concepts/daemon.md; the lock rationale lives in theinternal/daemon/lock.goanddaemon.gocomments. Regressions:TestAcquireSingletonLock_*,TestRunWithResources_SecondDaemonForSameRootFailsWithoutStealingSocket,TestRunWithOptions_RequiresSingletonLockBeforeRecovery,TestRecoverOnStartup_DoesNotDeleteActiveRunWorktree,TestServe_SecondListenerForLiveSocketDoesNotStealIt,TestDialConnectTimeoutFailsFastAndNamesSocket,TestIsRunningFailsFastWhenSocketAcceptsButDoesNotRespond,TestIsRunningSurfacesExistingDeadSocket,TestDaemonRunRootFromArgs_EnvDoesNotForceDaemonModeForProbes,TestValidateDaemonPIDFallback_RefusesToKillOwnProcess.
Bounded Daemon Logging and Event-Driven AXI Runs
internal/logstoreowns all daemon-process byte and retention bounds. Lifecycle output useslogs/daemon.log, managed Rovo Dev/OpenCode stdout and stderr uselogs/managed-server.log, and service bootstrap/direct crash output useslogs/daemon-bootstrap.log. Rotation snapshots backups and truncates the current inode in place so held service and child descriptors keep writing to the bounded current file. Regressions:internal/logstore/rotate_test.go,TestDetachedDaemonUsesBoundedDedicatedLogSinks,TestManagedServerOutputIsSeparatedFromLifecycleFailureSummary.- Successful read-only IPC methods are DEBUG; mutations and stream starts are INFO; every request failure is WARN. AXI run driving is subscribe-first and
internal/cli/run_reconciler.gois the sole owner of event reconciliation, reconnect, duplicate-event coalescing, and the slow lost-event heartbeat. Do not reintroduce fixed-intervalget_runpolling. A pre-driveget_active_runorget_runstate read that misses its per-attempt deadline is a slow reply, not a dead daemon: classify the timeout, health-probe, and retry.axi run/axi responddefault--wait 8mindependently so the hold cannot sit on a 10-minute harness cap, and subscription acknowledgement must honor that context. Regressions:TestSuccessfulReadRequestsDoNotLogAtInfo,TestRequestLoggingKeepsMutationsAndFailuresVisible,TestDriveRun_HealthyWaitStaysWithinRequestBudget,TestDriveRun_SlowGetRunRetriesAfterHealthProbe,TestAxiRun_SlowActiveRunReadRetriesInsteadOfStartingAnotherRun,TestAxiRespond_SlowInitialRunReadRetries,TestAxiRun_WaitInterruptsSubscriptionAcknowledgement,TestAxiRun_WaitElapsedAgainstLiveIdleDaemon,TestRunReconciler_*.
Bounded Loss-Aware Event Subscriptions
internal/ipc/events.go(ClassOf) is the single event taxonomy: activity is droppable, state is not, control is broker-generated, and an unrecognized type fails safe to state. Brokers and consumers must read loss tolerance from it rather than re-listing event names.internal/daemon/eventmailbox.gois the single overflow owner: a per-subscriber ring bounded by 64 events and 1 MiB, non-blocking publish (the executor is never stalled), activity as the only evictable class, and everything else folded into one sticky coalescingstream_gapthat drains ahead of queued payload. A reserved slot is not enough - it fails at the second simultaneous transition - and producer-side channel receives race the reader, which is why the queue is a ring under a mutex.- Every state event and every
get_runsnapshot carries a monotonicStateRev;runSnapshotsamples it before the DB read, which is sound only because every producer writes state and then emits. Consumers apply a delta only when its revision is newer, so a delta queued before a snapshot cannot regress state after it. Every subscription opens gapped, so attach and reconnect always reconcile first. - The fix-review working-tree diff is the only gate context that is never persisted, so it is served on demand by
ipc.MethodGetStepDiff(RunManager.StepDiff, bounded at 512 KiB) instead of riding the stream: it was the only unbounded payload, and one frame past the 1 MiB transport line limit ends the subscription and hides every later event. - Regressions:
internal/daemon/eventmailbox_test.go(A1-A13 plus the byte/count ceilings),TestRunSnapshot_*,TestStepDiff_*,TestExecutor_StateEventsAreEmittedAfterTheirDatabaseWrite,TestClassOfUnknownEventFailsSafeToState,TestRunReconciler_StreamGapForcesOneAuthoritativeRead,TestSubscribeOversizedFrameEndsTheStreamAndHidesLaterEvents,internal/tui/overflow_contract_test.go.
Destructive Daemon Lifecycle Guard (internal/lifecycle/guard.go)
daemon stop,daemon restart, andupdaterefuse by default while pending/running runs exist (the daemon is machine-wide, so stopping it can fail every active pipeline), list the runs via the sharedlifecycle.ActiveRuns/lifecycle.RunListhelpers, and require an explicit--force.update -yanswers only the different-executable prompt and deliberately does not bypass this guard.- Every invocation of the three commands is logged with caller attribution (PID, PPID, parent command line) via
logLifecycleInvocationto<NM_HOME>/logs/cli.log; this is the incident forensic trail, do not remove or weaken it. - Regressions:
TestDaemonStopRefusesWithActiveRunsAndListsThem,TestDaemonStopForceOverridesActiveRunGuard,TestDaemonRestartRefusesWithActiveRuns,TestLifecycleCommandsWriteCallerAttributionToCLILog(internal/cli/daemon_lifecycle_test.go),TestUpdaterRunRefusesWithActiveRunsAndListsThem,TestUpdaterActiveRunGuardAllowsForce(internal/update).
Signals
- GitHub stars
- 8k
- Forks
- 855
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
daemon-runtime- Source
- github.com/kunchenguid/no-mistakes