Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .nextchanges/cli/aitools-agents-skills.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`databricks aitools install` can now install skills into the vendor-neutral `.agents/skills/` location (`~/.agents/skills` globally, `<root>/.agents/skills` for project scope) via `--agents agents` or the interactive picker.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

=== install to the global .agents/skills destination
>>> [CLI] experimental aitools install --agents agents --scope=global
Command "install" is deprecated, use "databricks aitools install" instead.
Installing Databricks skills for Portable (.agents/skills)...
Using skills version test-ref
Fetching skills manifest...
Installed 1 skill.

=== a single global agent gets a real copied file under ~/.agents/skills
>>> find.py .agents/skills/test-stable-a/SKILL.md --expect 1
.agents/skills/test-stable-a/SKILL.md

>>> cat .agents/skills/test-stable-a/SKILL.md
---
name: test-stable-a
---

# A

=== install to the project .agents/skills destination (cwd)
>>> [CLI] experimental aitools install --agents agents --scope=project
Command "install" is deprecated, use "databricks aitools install" instead.
Installing Databricks skills for Portable (.agents/skills)...
Using skills version test-ref
Fetching skills manifest...
Installed 1 skill.

=== project scope writes the real file to the canonical dir
>>> find.py .databricks/aitools/skills/test-stable-a/SKILL.md --expect 1
.databricks/aitools/skills/test-stable-a/SKILL.md

=== and .agents/skills exposes the same content (symlinked into canonical)
>>> cat .agents/skills/test-stable-a/SKILL.md
---
name: test-stable-a
---

# A
19 changes: 19 additions & 0 deletions acceptance/experimental/aitools/skills/install-agents-dir/script
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Isolate HOME so parallel aitools tests don't race on a shared ~/.databricks.
sethome home

title "install to the global .agents/skills destination"
trace $CLI experimental aitools install --agents agents --scope=global

title "a single global agent gets a real copied file under ~/.agents/skills"
withdir "${USERPROFILE:-$HOME}" trace find.py '.agents/skills/test-stable-a/SKILL.md' --expect 1
withdir "${USERPROFILE:-$HOME}" trace cat .agents/skills/test-stable-a/SKILL.md

title "install to the project .agents/skills destination (cwd)"
mkdir -p proj
withdir proj trace $CLI experimental aitools install --agents agents --scope=project

title "project scope writes the real file to the canonical dir"
withdir proj trace find.py '.databricks/aitools/skills/test-stable-a/SKILL.md' --expect 1

title "and .agents/skills exposes the same content (symlinked into canonical)"
withdir proj trace cat .agents/skills/test-stable-a/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
Env.DATABRICKS_SKILLS_BASE_URL = "$DATABRICKS_HOST"
Env.DATABRICKS_SKILLS_REF = "test-ref"

Ignore = [
"home",
"proj",
]

EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"]

[[Server]]
Pattern = "GET /test-ref/manifest.json"
Response.Body = '''
{
"version": "2",
"updated_at": "2026-01-01T00:00:00Z",
"skills": {
"test-stable-a": {"version": "1.0.0", "files": ["SKILL.md"], "repo_dir": "skills"}
}
}
'''

[[Server]]
Pattern = "GET /test-ref/skills/test-stable-a/SKILL.md"
Response.Body = '''---
name: test-stable-a
---

# A
'''
2 changes: 2 additions & 0 deletions cmd/aitools/telemetry.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ func agentType(name string) protos.AitoolsAgentType {
return protos.AitoolsAgentTypeCopilot
case agents.NameAntigravity:
return protos.AitoolsAgentTypeAntigravity
case agents.NameAgents:
return protos.AitoolsAgentTypeAgents
default:
return protos.AitoolsAgentTypeUnspecified
}
Expand Down
14 changes: 14 additions & 0 deletions libs/aitools/agents/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ const (
NameOpenCode = "opencode"
NameCopilot = "copilot"
NameAntigravity = "antigravity"
// NameAgents is the vendor-neutral ".agents/skills" destination rather than a
// coding agent. It has no CLI binary and no plugin: skill files placed in
// ~/.agents/skills (global) or <root>/.agents/skills (project) are read
// directly by any tool that follows the AGENTS.md/.agents convention.
NameAgents = "agents"
)

// Databricks plugin identity, shared across the agents that ship a plugin.
Expand Down Expand Up @@ -204,6 +209,15 @@ var Registry = []*Agent{
SkillsSubdir: "global_skills",
// Antigravity is IDE-only with no CLI binary, so it has no plugin path.
},
{
Name: NameAgents,
DisplayName: "Portable (.agents/skills)",
ConfigDir: homeSubdir(".agents"),
SupportsProjectScope: true,
ProjectConfigDir: ".agents",
// A destination convention, not a coding agent: no CLI binary and no
// plugin, so raw skill files are its only delivery.
},
}

// openCodeConfigDir returns OpenCode's config directory. OpenCode stores its
Expand Down
32 changes: 32 additions & 0 deletions libs/aitools/agents/agents_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package agents

import (
"path/filepath"
"testing"

"github.com/databricks/cli/libs/env"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestAgentsPseudoAgentResolvesDirs(t *testing.T) {
a := ByName(NameAgents)
require.NotNil(t, a)

// Files-only destination: no CLI binary and no plugin.
assert.Empty(t, a.Binary)
assert.Nil(t, a.Plugin)
assert.True(t, a.SupportsProjectScope)

home := t.TempDir()
ctx := env.Set(t.Context(), "HOME", home)
// USERPROFILE drives env.UserHomeDir on Windows.
ctx = env.Set(ctx, "USERPROFILE", home)

globalDir, err := a.SkillsDir(ctx)
require.NoError(t, err)
assert.Equal(t, filepath.Join(home, ".agents", "skills"), globalDir)

projectDir := a.ProjectSkillsDir("/repo")
assert.Equal(t, filepath.Join("/repo", ".agents", "skills"), projectDir)
}
1 change: 1 addition & 0 deletions libs/aitools/installer/installer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1029,6 +1029,7 @@ func TestSupportsProjectScopeSetCorrectly(t *testing.T) {
"opencode": false,
"copilot": false,
"antigravity": false,
"agents": true,
}

for _, agent := range agents.Registry {
Expand Down
1 change: 1 addition & 0 deletions libs/telemetry/protos/aitools_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const (
AitoolsAgentTypeOpenCode AitoolsAgentType = "OPENCODE"
AitoolsAgentTypeCopilot AitoolsAgentType = "COPILOT"
AitoolsAgentTypeAntigravity AitoolsAgentType = "ANTIGRAVITY"
AitoolsAgentTypeAgents AitoolsAgentType = "AGENTS"
)

// AitoolsInstallScope mirrors AitoolsInstallScope.Type in the databricks_cli
Expand Down
Loading