feat: hot-reload skills in long-running agents - #2
Merged
Merged
Conversation
Long-running Agents cache discovered skills at instance start, so updating the cloned files on disk is invisible to a running OpenCode server. Skills could not be kept in sync at all: Git contexts had a sync option, skill sources did not. Add sync to GitSkillSource, reusing the existing GitSync type so the field shape matches Git contexts. Skills default to a 15m interval rather than the 5m used for contexts, because each applied change triggers a server instance reload and skill catalogs change less often. Add reload to GitSync for contexts to opt into the same re-scan behavior. Skill sources always reload when syncing, since re-scanning is the point. Rollout is rejected for skills: it needs the controller to compare remote refs, which is not implemented for authenticated repositories, and would otherwise be a silently ineffective option.
Populate the sync fields for skill sources in processSkills, mirroring the existing Git context handling, and mark skill mounts as needing a server re-scan after an update. Pass OPENCODE_RELOAD_URL to git-sync sidecars whose mount requests a reload, pointing at the agent's own server over loopback. Only Agent servers get it: Task pods are ephemeral and never build git-sync sidecars, so the reload concept does not apply there.
When a reload is requested, the sidecar asks the server to re-scan its configuration so updated skills take effect without a Pod restart. Disposal releases the whole server instance, so it must not run during an active turn: doing so aborts the user's work (verified: the assistant message ends with MessageAbortedError). The sidecar therefore checks session activity and defers the reload while any session is busy, retrying every cycle. The "server is behind" condition is persisted on the shared volume as the commit hash the server last scanned. In-memory tracking was not enough: a restarted sidecar would see an unchanged remote, conclude there was nothing to do, and leave the server stale until the repository changed again.
Unit tests for the reload path against a fake OpenCode server: idle disposes, busy refuses to dispose, disposal and status failures propagate, and malformed status payloads fail closed rather than being read as idle. Cover processSkills sync propagation (including the 15m default and implicit reload for skills), the sidecar reload env gating, and the server wiring that points the sidecar at the agent's own port. The server test uses a non-default port so a hardcoded URL would fail.
Document why file sync alone cannot update skills in a running Agent (OpenCode caches discovery at instance start) and how to enable the reload. Add ADR 0043 recording the mechanism, the measured reload cost, and the constraints: the idle gate that prevents aborting an active turn, the persisted state that survives a sidecar restart, the names-filtering limitation, and why plugins are out of scope.
nogoodusername
approved these changes
Sep 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes long-running Agents pick up skill changes from Git without restarting the Pod.
OpenCode discovers skills once, when its server instance starts, and caches them. So editing a skill repository does not reach a running Agent even after the cloned files on disk are updated. Git contexts already had a
syncoption; skill sources had none, so the only way to apply a skill change was to bounce the Agent's Deployment.This adds
syncto skill sources and implements the re-scan in the existinggit-syncsidecar. Because agit-syncsidecar is already created for every mount withsync.enabled, and skills already flow through the samegitMountpipeline as Git contexts, the plumbing is largely shared — the new part is the server re-scan.How it works
sync.enabledgets agit-syncsidecar, exactly as synced Git contexts do.The re-scan uses
POST /instance/dispose, which rebuilds the server instance on the next request. Verified behavior: skills are cached at instance start,disposemakes newly added and edited skills visible, the server stays healthy, and sessions survive it.Two things this gets right
A reload never aborts work in progress. Disposal during an active turn terminates it (the assistant message ends with
MessageAbortedError). The sidecar therefore checksGET /session/statusand only disposes when no session isbusy; a busy agent defers to the next cycle. The file update still happens immediately — only the re-scan waits.An owed reload survives a sidecar restart. My first cut tracked "reload pending" in memory. Live testing showed the failure: a sidecar restarting while a reload was deferred would see
local HEAD == remote HEAD, conclude there was nothing to do, and leave the server stale until the repository changed again — potentially days. The hash the server last scanned is now persisted on the shared volume (world-writable, for random-UID/SCC environments), so a restarted sidecar still knows the server is behind.API
syncadded toGitSkillSource, reusing the existingGitSynctype so the field shape matches Git contexts.reloadadded toGitSyncso Git contexts can opt into the same re-scan. Contexts keep today's behavior by default; skill sources always reload when syncing, since re-scanning is the point of syncing skills.Rolloutis rejected for skills via CEL. It would need the controller to compare remote refs, which is not implemented for authenticated repositories, and would otherwise be a silently ineffective option.CRD manifests and deepcopy regenerated (
make update-scripts update-crds).Measured cost
POST /instance/disposeAt a 15m interval the penalty is roughly 0.4% of one turn. Pre-warming after disposal was measured as ineffective. There is no lighter re-scan endpoint — disposal is the only mechanism the server exposes.
Scope
Reload applies to Agent servers only. Task pods are ephemeral, always start fresh, and never build
git-syncsidecars.Plugins are out of scope: plugin packages are installed into an emptyDir by
plugin-init, so a reload cannot make a new plugin'snode_modulesappear. That needs a separate install mechanism.Known limitation
A mount using
namesmaps fixed subpaths, so edits to mounted skills hot-reload but a newly added skill directory is not mounted until the Pod restarts. Omittingnamesmounts the whole directory and makes additions dynamic. Documented in the API and docs rather than worked around, since fixing it needs a different mount strategy.Tests
processSkillssync propagation, including the 15m default and implicit reload for skills.make lint(0 issues),make test(891 passed),make verifyall pass.opencode servewith the compiledgit-syncbinary: a pushed skill change reached the running server without a restart; a busy session caused deferral and left the in-flight turn untouched; the restart scenario above was reproduced and then verified fixed.E2E (
make e2e-setup) was not run; it needs the Kind environment.Docs
docs/adr/0043-hot-reload-skills-in-agents.md— mechanism, measured cost, constraints, alternatives.website/docs/features/skills.md,git-auto-sync.md,context-system.md— usage and field reference.