Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
23802ec
Add a generic agent-skills host with an access-aware preflight
kumo-isaac Oct 20, 2026
14a51ec
Generic host: bare file table names; write path destinations exactly
kumo-isaac Oct 20, 2026
8caea0e
Generic host: stage a file population with TIMESTAMP, in a created di…
kumo-isaac Oct 21, 2026
5a15087
Generic host: a timestamped entity's population must carry TIMESTAMP
kumo-isaac Oct 21, 2026
7ed8896
Generic host: stage a Databricks population through the statement API
kumo-isaac Oct 21, 2026
76cd6f7
Generic host: cast a population's TIMESTAMP to a timestamp
kumo-isaac Oct 21, 2026
feefa6d
Generic host: no region or profile placeholder for S3 and GCS
kumo-isaac Oct 21, 2026
ad5d748
S3Connector: region is optional and normally omitted
kumo-isaac Oct 21, 2026
e6df3db
Router: show the later-destination example as text, not a python block
kumo-isaac Oct 21, 2026
923874f
Genie Code host: hosts/databricks-genie, genie package with --deploy
kumo-isaac Oct 21, 2026
7d9829c
package_skill: genie --deploy --prune deletes what the package does n…
kumo-isaac Oct 21, 2026
05c018d
Genie host: cover the Snowflake case groups — registry, retention, jo…
kumo-isaac Oct 21, 2026
13f002d
Genie adapter: a metric view's primary keys are its base tables' Unit…
kumo-isaac Oct 21, 2026
e35d691
SDK reference: threshold and top-k belong to predict, never to Predic…
kumo-isaac Oct 21, 2026
b7a391d
batch_inference: never ask for a saved model's source; scoring option…
kumo-isaac Oct 23, 2026
b5d5245
Address review on the generic host
kumo-isaac Oct 23, 2026
f0a5b31
Merge PR #48's review changes into the Genie host
kumo-isaac Oct 24, 2026
f163844
agent-skills: address remaining quick review comments
kumo-isaac Oct 24, 2026
79d5f45
agent-skills: CSV time column is Dtype.date in the connector notes
kumo-isaac Oct 26, 2026
bac10d9
agent-skills: stage a scoring population in the source's system
kumo-isaac Oct 26, 2026
e1ce213
agent-skills: stage a BigQuery scoring population through the BigQuer…
kumo-isaac Oct 26, 2026
d1cf34e
agent-skills: pick the population connector from the population path
kumo-isaac Oct 26, 2026
d4e2c81
Merge PR #48's later review changes into the Genie host
kumo-isaac Oct 26, 2026
86a1ba8
Genie host: address the adversarial review (staging volume, deploy, s…
kumo-isaac Oct 26, 2026
5ba5655
Merge branch 'main' into igutierrez/agent-skills-generic-host
kumo-isaac Oct 26, 2026
4f974c5
agent-skills: generic BigQuery connectors name kumo.BigQueryIdentity()
kumo-isaac Oct 26, 2026
f8cc57d
Merge branch 'igutierrez/agent-skills-generic-host' into igutierrez/a…
kumo-isaac Oct 27, 2026
10b5dc8
Ship the agent skill in the kumo-engine wheel: kumo.engine.install_sk…
kumo-isaac Oct 27, 2026
9cd96a0
install_skills: address review (atomic installs, Genie prune, archive…
kumo-isaac Oct 29, 2026
366657f
install_skills: prepare fresh Snowsight workspaces, prune stale files…
kumo-isaac Oct 29, 2026
e9cae49
install_skills: yapf
kumo-isaac Oct 29, 2026
bcb835b
install_skills: complete the Genie inventory before pruning; quote th…
kumo-isaac Oct 30, 2026
f36b89b
Merge main (#111 squash-merged) into igutierrez/agent-skills-install
kumo-isaac Oct 30, 2026
d21ec19
Attach kumo-data-science skill zips to each kumo-engine release
kumo-isaac Oct 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .claude/commands/skill-zips.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
name: skill-zips
description: Check, or rebuild and attach, the kumo-data-science agent skill zips on a kumo-engine GitHub release.
---

# Skill zips

Every `kumo-engine-v<version>` release carries one agent skill zip per host, attached by the
`skill-zips` job in `.github/workflows/kumo-engine-wheel.yml`:
`kumo-data-science-coco.zip`, `kumo-data-science-generic.zip`, `kumo-data-science-genie.zip`.
The docs download button and install scripts link to them by tag. Use this skill to confirm a
release's zips are there and correct, or to attach them when the job did not.

Argument: the version, e.g. `1.1.0`. The tag is `kumo-engine-v<version>`.

## Steps

1. List the release's assets:
`gh release view kumo-engine-v<version> -R NVIDIA/kumo-engine --json assets --jq '.assets[].name'`

2. Rebuild the zips from the tag, in a scratch worktree so no local edit leaks into the build:
```bash
git fetch origin tag kumo-engine-v<version>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Major · Data Integrity & Integration · Quick win

Fetch the tag from the release repository.

If origin is a fork that lacks the release tag, this step fails. If the fork has a different commit under that tag, the command can publish the fork’s skill to the NVIDIA release after approval. Fetch the tag from NVIDIA/kumo-engine and build its resolved commit. (git-scm.com)

git worktree add --detach <scratch>/tag kumo-engine-v<version>
python3 <scratch>/tag/agent-skills/scripts/package_skill.py --zip <scratch>/built
```
The tag must carry `package_skill.py --zip`. A release cut before it has no skill zips and
cannot be given any this way; say so and stop.

3. If the release has all three zips, download them into a new empty directory
(`gh release download kumo-engine-v<version> -R NVIDIA/kumo-engine -p 'kumo-data-science-*.zip' -D <scratch>/released`)
and compare each with its rebuild (`cmp`). The build is reproducible, so any difference means
the attached zip was not built from the tag. Report the result, with the `SKILL-BUILD` line from
each zip's `kumo-data-science/SKILL.md`.

4. If any zip is missing, show the user the rebuilt zips (file counts, `SKILL-BUILD`) and **ask
before uploading**: attaching to a public release publishes the files. On approval:
`gh release upload kumo-engine-v<version> -R NVIDIA/kumo-engine <scratch>/built/*.zip`

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Major · Functional Correctness · Quick win

Upload only the missing ZIPs.

If a release already has one or two ZIPs, this glob also selects those existing assets. gh release upload rejects the command before uploading the missing ZIPs. Compare the existing assets, then upload only the missing files. (raw.githubusercontent.com)

Never pass `--clobber` over a zip that step 3 found to differ without telling the user first.

5. Remove the scratch worktree: `git worktree remove <scratch>/tag`.

Never upload zips built from a branch, or from a checkout with local changes: the zip must be the
skill built from the release's own commit, as the wheel's copy is.
45 changes: 42 additions & 3 deletions .github/workflows/kumo-engine-wheel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
# 3. This workflow builds the wheels at that tag and attaches them to the
# release once `check-requirements` shows each wheel installs from
# PyPI, alone and with each extra, on every Python version it claims.
# The agent skill zips (`kumo-data-science-<host>.zip`, built from the
# same commit by `skill-zips`) are attached beside them; the docs and
# install scripts link to them by release tag.
# 4. Once the `kumo-engine-release` environment approves it, `kitmaker`
# publishes the attached wheels to PyPI and pypi.nvidia.com through
# Kitmaker (scripts/kitmaker_release.py).
Expand Down Expand Up @@ -129,6 +132,36 @@ jobs:
retention-days: ${{ inputs.retention_days || 30 }}
if-no-files-found: error

# The kumo-data-science agent skill, one zip per host, for anyone who installs it without the
# wheel: a docs download link or an install script. The wheel carries the same packages for
# kumo.engine.install_skills(); both are built from this commit.
skill-zips:
if: >-
${{ github.event_name == 'workflow_dispatch'
|| startsWith(github.event.release.tag_name, 'kumo-engine-v') }}
runs-on: ubuntu-24.04
steps:
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: '3.12'

- name: Build the skill zips
run: python3 agent-skills/scripts/package_skill.py --zip dist

- name: Upload skill zips
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: kumo-data-science-zips-${{ github.sha }}
path: dist/*.zip
retention-days: ${{ inputs.retention_days || 30 }}
if-no-files-found: error

check-requirements:
needs: [build]
strategy:
Expand Down Expand Up @@ -166,7 +199,7 @@ jobs:
dist/kumo_engine-*.whl --platform "${WHEEL_PLATFORM}"

attach-to-release:
needs: [build, check-requirements]
needs: [build, check-requirements, skill-zips]
if: ${{ github.event_name == 'release' }}
runs-on: ubuntu-24.04
permissions:
Expand All @@ -179,12 +212,18 @@ jobs:
merge-multiple: true
path: dist

- name: Attach wheel to the GitHub release
- name: Download skill zips
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: kumo-data-science-zips-${{ github.sha }}
path: dist

- name: Attach wheel and skill zips to the GitHub release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: gh release upload "${RELEASE_TAG}" dist/*.whl --clobber
run: gh release upload "${RELEASE_TAG}" dist/*.whl dist/*.zip --clobber

kitmaker:
needs: [attach-to-release]
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,6 @@ docker/compose/config/generated/
pytest-of-*/

/public-reports/

# Built by packages/kumo-engine/setup.py from agent-skills/; ships in the wheel.
packages/kumo-engine/kumo/engine/_skills/
45 changes: 45 additions & 0 deletions agent-skills/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ python3 agent-skills/scripts/package_skill.py generic # -> agent-skills/dist
python3 agent-skills/scripts/package_skill.py genie # -> agent-skills/dist/genie/skills/
python3 agent-skills/scripts/package_skill.py genie --deploy # + upload to your Genie Code skills folder
python3 agent-skills/scripts/package_skill.py --check # build every host, verify references
python3 agent-skills/scripts/package_skill.py --zip DIR # kumo-data-science-<host>.zip for every host
```

A package carries exactly one host, and the core reaches it only as `hosts/<host>/`; tests fail if
Expand All @@ -53,6 +54,50 @@ uploads the `genie` package to `/Users/<you>/.assistant/skills/kumo-data-science
workspace named by `DATABRICKS_HOST` and `DATABRICKS_TOKEN` and reads every file back; a new Genie
Code chat loads it.

## Shipping in the `kumo-engine` wheel

Users install the skill from Kumo itself, so it always matches the installed SDK:

```python
import kumo.engine as kumo
kumo.install_skills() # Genie on Databricks; else every local agent found
kumo.install_skills('claude') # ~/.claude/skills/ (scope='project': ./.claude/skills/)
kumo.install_skills('codex') # ~/.agents/skills/ (scope='project': ./.agents/skills/)
kumo.install_skills('coco') # ~/.snowflake/cortex/skills/ (scope='project': ./.cortex/skills/)
kumo.install_skills('coco', scope='workspace', workspace='MY_WS') # a Snowsight workspace
kumo.install_skills('coco', scope='extension') # a Cortex Extension
kumo.install_skills('genie') # /Users/<you>/.assistant/skills/
kumo.install_skills(path='...') # any other agent's skills directory (generic package)
```

`python -m kumo.engine.skills [agent] [--scope ...]` does the same from a shell. Claude Code and
Codex get the `generic` package, CoCo `coco`, Genie `genie`.

`packages/kumo-engine/setup.py` calls `package_skill.bundle()` on every build from a checkout,
writing all three packages to `kumo/engine/_skills/` (gitignored), which the wheel ships as package
data. In a source checkout `install_skills` builds from `agent-skills/` directly instead, so an
edit is installed without a rebuild. Each install replaces only its own `kumo-data-science/`
directory. `tests/test_install_skills.py` covers every agent and scope; the Genie and Snowflake
account paths run there against fakes.

## Download zips on each release

For anyone installing the skill without the wheel (a docs download button, an install script),
every `kumo-engine-v*` GitHub release carries one zip per host, attached by the `skill-zips` job in
`.github/workflows/kumo-engine-wheel.yml` beside the wheels built from the same commit:

```
https://github.com/NVIDIA/kumo-engine/releases/download/kumo-engine-v<version>/kumo-data-science-generic.zip
https://github.com/NVIDIA/kumo-engine/releases/download/kumo-engine-v<version>/kumo-data-science-coco.zip
https://github.com/NVIDIA/kumo-engine/releases/download/kumo-engine-v<version>/kumo-data-science-genie.zip
```

Each zip holds `kumo-data-science/` at its root, the directory `install_skills()` installs: unzip it
into the agent's skills directory (the same locations as above). Link by release tag, never through
`releases/latest`: this repository also publishes `kumo-sdk-v*`, model and wheel releases, and
whichever is newest is "latest". The zips are reproducible, so `/skill-zips` can rebuild a
release's zips from its tag and compare them byte for byte.

The build stamps the router's `SKILL-BUILD` marker with the source commit
(`<commit date>-<short sha>`, `-dirty` for uncommitted edits); never edit it by hand. Every build
fails if a markdown link or a backticked package path does not resolve inside the
Expand Down
86 changes: 83 additions & 3 deletions agent-skills/scripts/package_skill.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
python3 agent-skills/scripts/package_skill.py genie --deploy # + upload to your Genie skills folder
python3 agent-skills/scripts/package_skill.py coco --out DIR # build somewhere else
python3 agent-skills/scripts/package_skill.py --check # build every host in a temp dir
python3 agent-skills/scripts/package_skill.py --zip DIR # one download zip per host

The source tree under `skills/` is not the layout that ships. A package holds one skill directory,
`kumo-data-science/`, and every other source directory nests inside it, so the router reaches
Expand All @@ -27,6 +28,12 @@
package does not ship are reported, and deleted with `--prune` (only inside
`kumo-data-science/`). A new Genie Code chat picks up the change.

`--zip DIR` writes `kumo-data-science-<host>.zip` for every host, each holding the one
`kumo-data-science/` directory at its root: unzip it into an agent's skills directory, or upload it
where an agent takes a skill zip. The release workflow attaches them to each `kumo-engine-v*`
GitHub release, beside the wheel built from the same commit. Entries are sorted and carry a fixed
timestamp, so one commit always yields the same bytes.

Every build is checked: each markdown link must resolve relative to the file that holds it, and
each backticked path naming a package directory must resolve relative to the package root. A
package that references a file it does not ship fails the build.
Expand All @@ -45,6 +52,7 @@
import urllib.error
import urllib.parse
import urllib.request
import zipfile
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
Expand Down Expand Up @@ -97,14 +105,31 @@ def git(*args: str) -> str:
return f'{stamp}-dirty' if dirty else stamp


SKILL_SUFFIXES = ('.md', '.py')


def git_tracked(src: Path) -> list[Path]:
"""List the files git tracks under `src`, as absolute paths."""
"""List the files git tracks under `src`, as absolute paths.

Without git — a source archive, extracted — fall back to the skill's own file types, so a
stray `.env` or log still cannot ship. `tests/test_package_skill.py` checks that the two lists
agree on the committed tree.
"""
rel = src.relative_to(ROOT)
out = subprocess.run(['git', '-C', str(ROOT), 'ls-files', '--', str(rel)],
check=True, capture_output=True, text=True).stdout
try:
out = subprocess.run(['git', '-C', str(ROOT), 'ls-files', '--', str(rel)],
check=True, capture_output=True, text=True).stdout
except (OSError, subprocess.CalledProcessError):
return skill_files(src)
return [ROOT / line for line in out.splitlines() if line]


def skill_files(src: Path) -> list[Path]:
"""The files under `src` a skill is made of, found without git."""
return sorted(p for p in src.rglob('*')
if p.is_file() and p.suffix in SKILL_SUFFIXES and '__pycache__' not in p.parts)


def copy_tracked(src: Path, dest: Path) -> None:
"""Copy only the files git tracks under `src` into `dest`, preserving structure.

Expand Down Expand Up @@ -170,6 +195,52 @@ def unresolved(pkg: Path) -> list[str]:
return errors


def bundle(out: Path) -> None:
"""Build every host's package into `out/<host>/kumo-data-science/`, the layout the
`kumo-engine` wheel ships under `kumo/engine/_skills/` for `kumo.engine.install_skills()`.

`packages/kumo-engine/setup.py` calls this on every build from a checkout, so a wheel always
carries the skill built from its own commit. A package with an unresolved reference fails the
build instead of shipping.
"""
if out.exists():
shutil.rmtree(out)
with tempfile.TemporaryDirectory() as tmp:
for host in sorted(HOSTS):
pkg = build(host, Path(tmp))
errors = unresolved(pkg)
if errors:
raise SystemExit(f'{host}: {len(errors)} unresolved reference(s): {errors[:3]}')
shutil.copytree(pkg, out / host / ROUTER)


# A zip entry's timestamp: the earliest a zip can hold, so a rebuild never changes the bytes.
ZIP_EPOCH = (1980, 1, 1, 0, 0, 0)


def archive(out: Path) -> list[Path]:
"""Write `out/kumo-data-science-<host>.zip` for every host and return the paths.

Each zip is the `bundle()` layout for one host: `kumo-data-science/` at its root, the same
directory `kumo.engine.install_skills()` installs.
"""
out.mkdir(parents=True, exist_ok=True)
zips = []
with tempfile.TemporaryDirectory() as tmp:
bundle(Path(tmp) / 'bundle')
for host in sorted(HOSTS):
root = Path(tmp) / 'bundle' / host
dest = out / f'{ROUTER}-{host}.zip'
with zipfile.ZipFile(dest, 'w', zipfile.ZIP_DEFLATED) as zf:
for p in sorted(p for p in root.rglob('*') if p.is_file()):
info = zipfile.ZipInfo(p.relative_to(root).as_posix(), ZIP_EPOCH)
info.compress_type = zipfile.ZIP_DEFLATED
info.external_attr = 0o644 << 16
zf.writestr(info, p.read_bytes())
zips.append(dest)
return zips


class Workspace:
"""The few Databricks REST calls a deploy needs, with a personal access token."""

Expand Down Expand Up @@ -284,12 +355,21 @@ def main() -> None:
ap.add_argument('host', nargs='?', choices=sorted(HOSTS))
ap.add_argument('--out', type=Path, default=DIST, help='output root (default: %(default)s)')
ap.add_argument('--check', action='store_true', help='build and check every host in a temp dir')
ap.add_argument('--zip', type=Path, metavar='DIR',
help='write kumo-data-science-<host>.zip for every host into DIR')
ap.add_argument('--deploy', action='store_true',
help='genie: upload to your Genie Code skills folder and read it back')
ap.add_argument('--prune', action='store_true',
help='genie --deploy: delete files in the target the package does not ship')
ap.add_argument('--env-file', type=Path, help='KEY=VALUE file with DATABRICKS_HOST/_TOKEN')
args = ap.parse_args()
if args.zip:
if args.host or args.check or args.deploy:
ap.error('--zip builds every host; give it alone')
for z in archive(args.zip):
with zipfile.ZipFile(z) as zf:
print(f'{z}: {len(zf.namelist())} files, build {build_id()}')
return
if not args.check and not args.host:
ap.error('give a host, or --check')
if args.deploy and (args.check or args.host != 'genie'):
Expand Down
Loading
Loading