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:
| Phase | Typical time | Notes |
|---|---|---|
| Filesystem event delivery | < 5 ms | OS-level inotify / FSEvents / ReadDirectoryChangesW |
| Debounce window | 0–1000 ms | Up to debounceMs waiting for additional saves in the batch |
| TypeScript compilation | 50–300 ms | Depends on file size and import depth; cached incremental builds are faster |
| Export validation | < 10 ms | Checks that required exports (e.g. execute, definition) exist |
| Atomic swap | < 1 ms | Reference 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:
- 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 - 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
- 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
hotReload:
enabled: true
watchDirs:
- skills/
- plugins/
debounceMs: 1000 # Coalesce rapid saves into a single reloadWhat gets reloaded
Hot reload applies selectively. Only stateless, file-defined modules are eligible:
| Module | Hot reloaded | Notes |
|---|---|---|
| Skills | Yes | Reloaded on any change inside skills/ |
| Plugins | Yes | Reloaded on any change inside plugins/ |
| Agents | No | Require a full gateway restart to reload |
| Core gateway | No | Binary; must be restarted or redeployed |
Failure modes
The reload pipeline has three failure points, each with a defined behavior:
| Failure point | Behavior | Previous version? |
|---|---|---|
| Compilation error (syntax error, type error) | Reload aborted; error logged with file + line number | Remains active |
Missing required export (execute, definition) | Reload aborted; validation error logged | Remains active |
| Runtime error during module initialization | Reload aborted; stack trace logged | Remains active |
| File deleted | Skill is unregistered from the active registry; no fallback | Removed — 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
# 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=524288Debugging hot reload
Set LOG_LEVEL=debug to see the full reload pipeline in the gateway logs, including debounce timing, compilation steps, and swap confirmation:
# 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 activehotReload.enabled: false in production if your deployment pipeline handles restarts. Hot reload is most useful during local development where fast iteration matters.