feat(skills): promote Agent Skills to stable (#17693)

This commit is contained in:
Abhi
2026-01-27 16:50:22 -05:00
committed by Abhi
parent 2a3c879782
commit e6a51901a7
18 changed files with 79 additions and 253 deletions

View File

@@ -1,5 +0,0 @@
{
"experimental": {
"skills": true
}
}

View File

@@ -205,8 +205,8 @@ Slash commands provide meta-level control over the CLI itself.
while others require a restart.
- [**`/skills`**](./skills.md)
- **Description:** (Experimental) Manage Agent Skills, which provide on-demand
expertise and specialized workflows.
- **Description:** Manage Agent Skills, which provide on-demand expertise and
specialized workflows.
- **Sub-commands:**
- **`list`**:
- **Description:** List all discovered skills and their current status

View File

@@ -29,8 +29,8 @@ overview of Gemini CLI, see the [main documentation page](../index.md).
in an enterprise environment.
- **[Sandboxing](./sandbox.md):** Isolate tool execution in a secure,
containerized environment.
- **[Agent Skills](./skills.md):** (Experimental) Extend the CLI with
specialized expertise and procedural workflows.
- **[Agent Skills](./skills.md):** Extend the CLI with specialized expertise and
procedural workflows.
- **[Telemetry](./telemetry.md):** Configure observability to monitor usage and
performance.
- **[Token caching](./token-caching.md):** Optimize API costs by caching tokens.

View File

@@ -115,13 +115,18 @@ they appear in the UI.
| UI Label | Setting | Description | Default |
| ----------------------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------- | ------- |
| Agent Skills | `experimental.skills` | Enable Agent Skills (experimental). | `false` |
| Enable Codebase Investigator | `experimental.codebaseInvestigatorSettings.enabled` | Enable the Codebase Investigator agent. | `true` |
| Codebase Investigator Max Num Turns | `experimental.codebaseInvestigatorSettings.maxNumTurns` | Maximum number of turns for the Codebase Investigator agent. | `10` |
| Use OSC 52 Paste | `experimental.useOSC52Paste` | Use OSC 52 sequence for pasting instead of clipboardy (useful for remote sessions). | `false` |
| Enable CLI Help Agent | `experimental.cliHelpAgentSettings.enabled` | Enable the CLI Help Agent. | `true` |
| Plan | `experimental.plan` | Enable planning features (Plan Mode and tools). | `false` |
### Skills
| UI Label | Setting | Description | Default |
| ------------------- | ---------------- | -------------------- | ------- |
| Enable Agent Skills | `skills.enabled` | Enable Agent Skills. | `true` |
### HooksConfig
| UI Label | Setting | Description | Default |

View File

@@ -1,9 +1,5 @@
# Agent Skills
_Note: This is an experimental feature enabled via `experimental.skills`. You
can also search for "Skills" within the `/settings` interactive UI to toggle
this and manage other skill-related settings._
Agent Skills allow you to extend Gemini CLI with specialized expertise,
procedural workflows, and task-specific resources. Based on the
[Agent Skills](https://agentskills.io) open standard, a "skill" is a

View File

@@ -1,37 +1,10 @@
# Getting Started with Agent Skills
Agent Skills allow you to extend Gemini CLI with specialized expertise. This
tutorial will guide you through creating your first skill, enabling it, and
using it in a session.
tutorial will guide you through creating your first skill and using it in a
session.
## 1. Enable Agent Skills
Agent Skills are currently an experimental feature and must be enabled in your
settings.
### Via the interactive UI
1. Start a Gemini CLI session by running `gemini`.
2. Type `/settings` to open the interactive settings dialog.
3. Search for "Skills".
4. Toggle **Agent Skills** to `true`.
5. Press `Esc` to save and exit. You may need to restart the CLI for the
changes to take effect.
### Via `settings.json`
Alternatively, you can manually edit your global settings file at
`~/.gemini/settings.json` (create it if it doesn't exist):
```json
{
"experimental": {
"skills": true
}
}
```
## 2. Create Your First Skill
## 1. Create your first skill
A skill is a directory containing a `SKILL.md` file. Let's create an **API
Auditor** skill that helps you verify if local or remote endpoints are
@@ -86,7 +59,7 @@ responding correctly.
.catch((e) => console.error(`Result: Failed (${e.message})`));
```
## 3. Verify the Skill is Discovered
## 2. Verify the skill is discovered
Use the `/skills` slash command (or `gemini skills list` from your terminal) to
see if Gemini CLI has found your new skill.
@@ -99,7 +72,7 @@ In a Gemini CLI session:
You should see `api-auditor` in the list of available skills.
## 4. Use the Skill in a Chat
## 3. Use the skill in a chat
Now, let's see the skill in action. Start a new session and ask a question about
an endpoint.

View File

@@ -224,8 +224,6 @@ file in every session where the extension is active.
## (Optional) Step 6: Add an Agent Skill
_Note: This is an experimental feature enabled via `experimental.skills`._
[Agent Skills](../cli/skills.md) let you bundle specialized expertise and
procedural workflows. Unlike `GEMINI.md`, which provides persistent context,
skills are activated only when needed, saving context tokens.

View File

@@ -850,11 +850,6 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `false`
- **Requires restart:** Yes
- **`experimental.skills`** (boolean):
- **Description:** Enable Agent Skills (experimental).
- **Default:** `false`
- **Requires restart:** Yes
- **`experimental.codebaseInvestigatorSettings.enabled`** (boolean):
- **Description:** Enable the Codebase Investigator agent.
- **Default:** `true`
@@ -899,6 +894,11 @@ their corresponding top-level category object in your `settings.json` file.
#### `skills`
- **`skills.enabled`** (boolean):
- **Description:** Enable Agent Skills.
- **Default:** `true`
- **Requires restart:** Yes
- **`skills.disabled`** (array):
- **Description:** List of disabled skills.
- **Default:** `[]`

View File

@@ -56,8 +56,8 @@ This documentation is organized into the following sections:
commands with `/model`.
- **[Sandbox](./cli/sandbox.md):** Isolate tool execution in a secure,
containerized environment.
- **[Agent Skills](./cli/skills.md):** (Experimental) Extend the CLI with
specialized expertise and procedural workflows.
- **[Agent Skills](./cli/skills.md):** Extend the CLI with specialized expertise
and procedural workflows.
- **[Settings](./cli/settings.md):** Configure various aspects of the CLI's
behavior and appearance with `/settings`.
- **[Telemetry](./cli/telemetry.md):** Overview of telemetry in the CLI.

View File

@@ -204,21 +204,18 @@
{
"label": "Hooks",
"items": [
{ "label": "Getting started", "slug": "docs/hooks/getting-started" },
{ "label": "Hook types", "slug": "docs/hooks/hook-types" },
{ "label": "API reference", "slug": "docs/hooks/api-reference" }
]
},
{
"label": "Ecosystem and extensibility",
"items": [
{ "label": "Agent skills", "slug": "docs/cli/skills" },
{
"label": "Introduction",
"slug": "docs/hooks"
},
{
"label": "Writing hooks",
"slug": "docs/hooks/writing-hooks"
},
{
"label": "Hooks reference",
"slug": "docs/hooks/reference"
},
{
"label": "Best practices",
"slug": "docs/hooks/best-practices"
"label": "Sub-agents (experimental)",
"slug": "docs/core/subagents"
}
]
},

View File

@@ -91,8 +91,8 @@ Additionally, these tools incorporate:
- **[MCP servers](./mcp-server.md)**: MCP servers act as a bridge between the
Gemini model and your local environment or other services like APIs.
- **[Agent Skills](../cli/skills.md)**: (Experimental) On-demand expertise
packages that are activated via the `activate_skill` tool to provide
specialized guidance and resources.
- **[Agent Skills](../cli/skills.md)**: On-demand expertise packages that are
activated via the `activate_skill` tool to provide specialized guidance and
resources.
- **[Sandboxing](../cli/sandbox.md)**: Sandboxing isolates the model and its
changes from your environment to reduce potential risk.

View File

@@ -534,6 +534,42 @@ describe('parseArguments', () => {
'Use whoami to write a poem in file poem.md about my username in pig latin and use wc to tell me how many lines are in the poem you wrote.',
);
});
it('should set isCommand to true for mcp command', async () => {
process.argv = ['node', 'script.js', 'mcp', 'list'];
const argv = await parseArguments(createTestMergedSettings());
expect(argv.isCommand).toBe(true);
});
it('should set isCommand to true for extensions command', async () => {
process.argv = ['node', 'script.js', 'extensions', 'list'];
// Extensions command uses experimental settings
const settings = createTestMergedSettings({
experimental: { extensionManagement: true },
});
const argv = await parseArguments(settings);
expect(argv.isCommand).toBe(true);
});
it('should set isCommand to true for skills command', async () => {
process.argv = ['node', 'script.js', 'skills', 'list'];
// Skills command enabled by default or via experimental
const settings = createTestMergedSettings({
skills: { enabled: true },
});
const argv = await parseArguments(settings);
expect(argv.isCommand).toBe(true);
});
it('should set isCommand to true for hooks command', async () => {
process.argv = ['node', 'script.js', 'hooks', 'migrate'];
// Hooks command enabled via tools settings
const settings = createTestMergedSettings({
tools: { enableHooks: true },
});
const argv = await parseArguments(settings);
expect(argv.isCommand).toBe(true);
});
});
describe('loadCliConfig', () => {

View File

@@ -289,7 +289,7 @@ export async function parseArguments(
yargsInstance.command(extensionsCommand);
}
if (settings.experimental?.skills || (settings.skills?.enabled ?? true)) {
if (settings.skills?.enabled ?? true) {
yargsInstance.command(skillsCommand);
}
// Register hooks command if hooks are enabled
@@ -725,8 +725,7 @@ export async function loadCliConfig(
plan: settings.experimental?.plan,
enableEventDrivenScheduler:
settings.experimental?.enableEventDrivenScheduler,
skillsSupport:
settings.experimental?.skills || (settings.skills?.enabled ?? true),
skillsSupport: settings.skills?.enabled ?? true,
disabledSkills: settings.skills?.disabled,
experimentalJitContext: settings.experimental?.jitContext,
noBrowser: !!process.env['NO_BROWSER'],

View File

@@ -1452,15 +1452,6 @@ const SETTINGS_SCHEMA = {
description: 'Enable Just-In-Time (JIT) context loading.',
showInDialog: false,
},
skills: {
type: 'boolean',
label: 'Agent Skills',
category: 'Experimental',
requiresRestart: true,
default: false,
description: 'Enable Agent Skills (experimental).',
showInDialog: true,
},
codebaseInvestigatorSettings: {
type: 'object',
label: 'Codebase Investigator Settings',
@@ -1615,7 +1606,6 @@ const SETTINGS_SCHEMA = {
default: true,
description: 'Enable Agent Skills.',
showInDialog: true,
ignoreInDocs: true,
},
disabled: {
type: 'array',

View File

@@ -1,158 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { loadCliConfig, parseArguments } from './config.js';
import * as trustedFolders from './trustedFolders.js';
import { loadServerHierarchicalMemory } from '@google/gemini-cli-core';
import { type Settings, createTestMergedSettings } from './settings.js';
vi.mock('./trustedFolders.js');
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const actual =
await importOriginal<typeof import('@google/gemini-cli-core')>();
return {
...actual,
loadServerHierarchicalMemory: vi.fn(),
getPty: vi.fn().mockResolvedValue({ name: 'test-pty' }),
getVersion: vi.fn().mockResolvedValue('0.0.0-test'),
};
});
describe('Agent Skills Backward Compatibility', () => {
const originalArgv = process.argv;
beforeEach(() => {
vi.resetAllMocks();
vi.mocked(trustedFolders.isWorkspaceTrusted).mockReturnValue({
isTrusted: true,
} as unknown as trustedFolders.TrustResult);
});
afterEach(() => {
process.argv = originalArgv;
});
describe('loadCliConfig', () => {
it('should default skillsSupport to true when no settings are present', async () => {
vi.mocked(loadServerHierarchicalMemory).mockResolvedValue({
memoryContent: '',
fileCount: 0,
filePaths: [],
});
process.argv = ['node', 'gemini'];
const settings = createTestMergedSettings({});
const config = await loadCliConfig(
settings,
'test-session',
await parseArguments(settings),
);
expect(
(
config as unknown as { isSkillsSupportEnabled: () => boolean }
).isSkillsSupportEnabled(),
).toBe(true);
});
it('should prioritize skills.enabled=false from settings', async () => {
vi.mocked(loadServerHierarchicalMemory).mockResolvedValue({
memoryContent: '',
fileCount: 0,
filePaths: [],
});
const settings = createTestMergedSettings({
skills: { enabled: false },
} as unknown as Settings);
process.argv = ['node', 'gemini'];
const config = await loadCliConfig(
settings,
'test-session',
await parseArguments(settings),
);
expect(
(
config as unknown as { isSkillsSupportEnabled: () => boolean }
).isSkillsSupportEnabled(),
).toBe(false);
});
it('should support legacy experimental.skills=true from settings', async () => {
vi.mocked(loadServerHierarchicalMemory).mockResolvedValue({
memoryContent: '',
fileCount: 0,
filePaths: [],
});
const settings = createTestMergedSettings({
experimental: { skills: true },
} as unknown as Settings);
process.argv = ['node', 'gemini'];
const config = await loadCliConfig(
settings,
'test-session',
await parseArguments(settings),
);
expect(
(
config as unknown as { isSkillsSupportEnabled: () => boolean }
).isSkillsSupportEnabled(),
).toBe(true);
});
it('should prioritize legacy experimental.skills=true over new skills.enabled=false', async () => {
vi.mocked(loadServerHierarchicalMemory).mockResolvedValue({
memoryContent: '',
fileCount: 0,
filePaths: [],
});
const settings = createTestMergedSettings({
skills: { enabled: false },
experimental: { skills: true },
} as unknown as Settings);
process.argv = ['node', 'gemini'];
const config = await loadCliConfig(
settings,
'test-session',
await parseArguments(settings),
);
expect(
(
config as unknown as { isSkillsSupportEnabled: () => boolean }
).isSkillsSupportEnabled(),
).toBe(true);
});
it('should still be enabled by default if legacy experimental.skills is false (since new default is true)', async () => {
vi.mocked(loadServerHierarchicalMemory).mockResolvedValue({
memoryContent: '',
fileCount: 0,
filePaths: [],
});
const settings = createTestMergedSettings({
experimental: { skills: false },
} as unknown as Settings);
process.argv = ['node', 'gemini'];
const config = await loadCliConfig(
settings,
'test-session',
await parseArguments(settings),
);
expect(
(
config as unknown as { isSkillsSupportEnabled: () => boolean }
).isSkillsSupportEnabled(),
).toBe(true);
});
});
});

View File

@@ -119,11 +119,12 @@ describe('BuiltinCommandLoader', () => {
getEnableHooks: () => false,
getEnableHooksUI: () => false,
getExtensionsEnabled: vi.fn().mockReturnValue(true),
isSkillsSupportEnabled: vi.fn().mockReturnValue(false),
isSkillsSupportEnabled: vi.fn().mockReturnValue(true),
isAgentsEnabled: vi.fn().mockReturnValue(false),
getMcpEnabled: vi.fn().mockReturnValue(true),
getSkillManager: vi.fn().mockReturnValue({
getAllSkills: vi.fn().mockReturnValue([]),
isAdminEnabled: vi.fn().mockReturnValue(true),
}),
} as unknown as Config;
@@ -260,11 +261,12 @@ describe('BuiltinCommandLoader profile', () => {
getEnableHooks: () => false,
getEnableHooksUI: () => false,
getExtensionsEnabled: vi.fn().mockReturnValue(true),
isSkillsSupportEnabled: vi.fn().mockReturnValue(false),
isSkillsSupportEnabled: vi.fn().mockReturnValue(true),
isAgentsEnabled: vi.fn().mockReturnValue(false),
getMcpEnabled: vi.fn().mockReturnValue(true),
getSkillManager: vi.fn().mockReturnValue({
getAllSkills: vi.fn().mockReturnValue([]),
isAdminEnabled: vi.fn().mockReturnValue(true),
}),
} as unknown as Config;
});

View File

@@ -634,7 +634,7 @@ export class Config {
this.planEnabled = params.plan ?? false;
this.enableEventDrivenScheduler =
params.enableEventDrivenScheduler ?? false;
this.skillsSupport = params.skillsSupport ?? false;
this.skillsSupport = params.skillsSupport ?? true;
this.disabledSkills = params.disabledSkills ?? [];
this.adminSkillsEnabled = params.adminSkillsEnabled ?? true;
this.modelAvailabilityService = new ModelAvailabilityService();

View File

@@ -1421,13 +1421,6 @@
"default": false,
"type": "boolean"
},
"skills": {
"title": "Agent Skills",
"description": "Enable Agent Skills (experimental).",
"markdownDescription": "Enable Agent Skills (experimental).\n\n- Category: `Experimental`\n- Requires restart: `yes`\n- Default: `false`",
"default": false,
"type": "boolean"
},
"codebaseInvestigatorSettings": {
"title": "Codebase Investigator Settings",
"description": "Configuration for Codebase Investigator.",