Agents

Hot Reload

Skills and plugins are live-reloaded when their files change on disk. The gateway watches the skills/ and plugins/ directories and reloads without restart. A 1000ms debounce prevents thrashing during rapid saves.

Detection latency

Hot reload targets under 1 second end-to-end from file save to active swap. The timeline breaks down as:

PhaseTypical timeNotes
Filesystem event delivery< 5 msOS-level inotify / FSEvents / ReadDirectoryChangesW
Debounce window0–1000 msUp to debounceMs waiting for additional saves in the batch
TypeScript compilation50–300 msDepends on file size and import depth; cached incremental builds are faster
Export validation< 10 msChecks that required exports (e.g. execute, definition) exist
Atomic swap< 1 msReference swap — zero downtime

In practice, the debounce window dominates. If you save a file and immediately trigger an agent turn, the old version serves that turn. The new version is active on the next turn after the debounce window closes. Reduce debounceMs to 100–200ms for faster feedback during development.

How it works

Hot reload operates through three coordinated mechanisms:

  1. Filesystem watcher — The gateway registers a recursive watcher on each directory listed in hotReload.watchDirs. Any file write, rename, or deletion event triggers the reload pipeline
  2. Debounce — Events are coalesced using a trailing-edge debounce (default 1000ms). If multiple files are saved within the window, a single reload is issued rather than one per file, preventing thrashing during editor bulk-saves
  3. Zero-downtime swap — The new module is compiled and validated in an isolated context. Only after successful validation is the live reference atomically swapped. In-flight requests continue using the previous version until they complete

Configuration

yaml
hotReload:
  enabled: true
  watchDirs:
    - skills/
    - plugins/
  debounceMs: 1000       # Coalesce rapid saves into a single reload

What gets reloaded

Hot reload applies selectively. Only stateless, file-defined modules are eligible:

ModuleHot reloadedNotes
SkillsYesReloaded on any change inside skills/
PluginsYesReloaded on any change inside plugins/
AgentsNoRequire a full gateway restart to reload
Core gatewayNoBinary; must be restarted or redeployed

Failure modes

The reload pipeline has three failure points, each with a defined behavior:

Failure pointBehaviorPrevious version?
Compilation error (syntax error, type error)Reload aborted; error logged with file + line numberRemains active
Missing required export (execute, definition)Reload aborted; validation error loggedRemains active
Runtime error during module initializationReload aborted; stack trace loggedRemains active
File deletedSkill is unregistered from the active registry; no fallbackRemoved — calls to this skill will 404

In all failure cases except deletion, the gateway continues serving requests with the previous working version. No restart is required. Fix the error, save the file, and the reload retries automatically.

Edge cases with partial updates

  • Save during a running turn. If a reload swap occurs while a turn is using that skill, the in-flight turn continues with the old version. The new version takes effect on the next turn. This is guaranteed by the atomic reference swap — there is no window where a turn can see a partially-loaded module.
  • Multiple files changed simultaneously. The debounce window coalesces all changes into a single reload. All changed files are recompiled together, so cross-file dependencies within the same batch are handled correctly.
  • Circular imports introduced by a change. TypeScript compilation will fail with a circular dependency error. The previous version remains active. Resolve the circular import and re-save.
  • File renamed. The old skill name is unregistered and the new name is registered as a new skill. Any agent config referencing the old name will produce a "skill not found" error until the agent config is updated.
  • Very large files (> 2000 lines). Compilation time may push total reload latency above 1 second. Consider splitting large skill files to stay within the target latency.

Limitations

  • In-memory state is reset — Any state held inside a skill or plugin module (e.g., cached values, counters) is discarded on reload. Skills should not rely on in-process state across reloads; use external storage if persistence is required
  • Watcher limits on Linux — The filesystem watcher consumes one inotify watch per file. On systems with a large number of skill files you may need to raise fs.inotify.max_user_watches
bash
# Check current inotify watch limit
cat /proc/sys/fs/inotify/max_user_watches

# Raise the limit (add to /etc/sysctl.conf for persistence)
sudo sysctl fs.inotify.max_user_watches=524288

Debugging hot reload

Set LOG_LEVEL=debug to see the full reload pipeline in the gateway logs, including debounce timing, compilation steps, and swap confirmation:

bash
# Enable verbose hot-reload logging
LOG_LEVEL=debug npx astra

# Example debug output:
# [hot-reload] change detected: skills/web-research.ts (modified)
# [hot-reload] debounce window: 1000ms — waiting for more changes
# [hot-reload] no further changes — compiling skills/web-research.ts
# [hot-reload] compilation OK — validating exports
# [hot-reload] swap complete: web-research (23ms total, 0 in-flight affected)

# If validation fails:
# [hot-reload] compilation FAILED: skills/web-research.ts
# [hot-reload] SyntaxError: Unexpected token at line 42
# [hot-reload] rollback: previous version of web-research remains active
Set hotReload.enabled: false in production if your deployment pipeline handles restarts. Hot reload is most useful during local development where fast iteration matters.