Skip to content

Auto-update downloaded skills at agent launch - #743

Draft
xsh310 wants to merge 1 commit into
databricks:mainfrom
xsh310:skills-launch-auto-update
Draft

xsh310 wants to merge 1 commit into
databricks:mainfrom
xsh310:skills-launch-auto-update

Conversation

@xsh310

@xsh310 xsh310 commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

What did you change, and why?

Change: ug <agent> now refreshes a developer's UC-downloaded skills before the agent starts. At launch it reads the download attribution manifest, and for each of the current workspace's own (non-managed) downloads under the home or working directory it calls GetSkill and compares the skill's current uc_update_time against the value recorded at download. Any skill whose Unity Catalog source is newer is re-downloaded in place, with no overwrite prompt, since only manifest-attributed directories are touched.

Why: A downloaded skill previously drifted from its Unity Catalog source until the developer re-ran ug skills add. This keeps downloads current automatically, the way Isaac keeps installed plugins current.

The sweep is rate-limited to once every 24 hours through a new top-level last_update_check key in ~/.ucode/skills.json, matching Isaac's plugin marketplace staleness window, so back-to-back launches make no network calls. It is best effort and fails open, so a network, auth, or fetch error is reported and the launch proceeds on whatever is already on disk. It is skipped under --skip-preflight.

How do you know it works?

Testing: Added unit tests covering the new manifest accessors and that recording downloads preserves the last_update_check stamp (test_skills_state.py), plus the launch path (test_skills_download.py): eligibility filtering (home and working dir, current workspace, managed excluded), stale detection by uc_update_time (newer, equal, older, missing, deleted), the silent in-place overwrite that refreshes both skill roots and the manifest record, and the entry point's rate-limit gate, fail-open behavior, and end-to-end update. uv run ruff check, ruff format --check, ty check src, and uv run pytest all pass locally.

This pull request and its description were written by Isaac.

Refresh a developer's UC-downloaded skills before `ug <agent>` launches, so a
skill whose Unity Catalog source changed since download is re-downloaded in
place without a manual `ug skills add`.

The check reuses the download attribution manifest (`~/.ucode/skills.json`).
Each install already records the skill's `uc_update_time` at download, and a
new top-level `last_update_check` rate-limits the sweep to once every 24 hours,
matching Isaac's plugin marketplace staleness window. Only the launching
workspace's own non-managed downloads under the home or working directory are
eligible; managed skills stay owned by `ug configure`. The sweep is best effort
and fails open, so it never blocks a launch.

Co-authored-by: Isaac <no-reply@databricks.com>
working dir qualify. Managed skills are left to ``ug configure``, and other workspaces'
downloads are skipped because the launch token authenticates only this workspace.
"""
bases = {os.path.normpath(str(Path.home())), os.path.normpath(cwd)}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this true? I think for claude code at least, the entire every directory in the directory path can have valid skills dirs right? Do you think we can just drop the base check for now for simplicity?

]


def _stale_launch_refs(

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can you use a more intuitive name? Maybe "get_updated_refs" sth like that?

return pairs


def _apply_launch_updates(workspace: str, token: str, pairs: list[tuple[dict, SkillRef]]) -> int:

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe call this update_stale_skills or something more intuitive and human understandable?

updated = 0
for base, refs in refs_by_base.items():
path = None if base == home else base
roots = skill_dir_roots(path)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an existing download and write util function, if not can we extract one? I think we've rewritten this in a few places?

return updated


def refresh_downloaded_skills_on_launch(state: dict) -> None:

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One edge case to consider, if a skill has been manually deleted by user but the record is still in manifest, should we still update this in this case? I think we should just remove the invalid record in that case, wdyt?

print_warning(f"Skipping `{ref.fqn}`: {reason}.")
continue
try:
write_skill(roots, ref, files)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this write_skill overwrite the existing files, right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant