Skip to content
Open
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
28 changes: 28 additions & 0 deletions docs/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -12076,6 +12076,18 @@
"$ref": "#/components/schemas/OkpConfiguration",
"title": "OKP configuration",
"description": "OKP provider settings. Only used when 'okp' is listed in rag.inline or rag.tool."
},
"skills": {
"anyOf": [
{
"$ref": "#/components/schemas/SkillsConfiguration"
},
{
"type": "null"
}
],
"title": "Agent skills",
"description": "Agent skills configuration. Specifies paths to skill directories."
}
},
"additionalProperties": false,
Expand Down Expand Up @@ -19344,6 +19356,22 @@
}
]
},
"SkillsConfiguration": {
"properties": {
"paths": {
"items": {
"type": "string"
},
"type": "array",
"title": "Skill paths",
"description": "Paths to skill directories or directories containing skill subdirectories."
}
},
"additionalProperties": false,
"type": "object",
"title": "SkillsConfiguration",
"description": "Agent skills configuration.\n\nSpecifies paths to skill directories. Skill metadata (name, description)\nis read from SKILL.md frontmatter at startup.\n\nEach path can point to either:\n- A directory containing a SKILL.md file (single skill)\n- A directory containing subdirectories with SKILL.md files (multiple skills)\n\nPaths are validated at startup to ensure they exist and contain valid SKILL.md files."
},
"SolrVectorSearchRequest": {
"properties": {
"mode": {
Expand Down
31 changes: 31 additions & 0 deletions examples/lightspeed-stack-skills.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Lightspeed Core Service (LCS) with Skills
service:
host: localhost
port: 8080
auth_enabled: false
workers: 1
color_log: true
access_log: true
llama_stack:
use_as_library_client: true
library_client_config_path: run.yaml
user_data_collection:
feedback_enabled: true
feedback_storage: "/tmp/data/feedback"
transcripts_enabled: true
transcripts_storage: "/tmp/data/transcripts"
authentication:
module: "noop"
# Agent skills configuration
# Skills provide domain-specific instructions and reference materials
# that the LLM can load on demand when relevant to the current task
skills:
paths:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is there a reason that we need the paths? It would make sense if we had other data fields under skills but if paths is the only one then I think skills can just be a list. wdyt?

Copy link
Copy Markdown
Contributor Author

@anik120 anik120 May 13, 2026

Choose a reason for hiding this comment

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

Yea I think that makes total sense. I was going off of what's here https://github.com/lightspeed-core/lightspeed-stack/blob/main/docs/design/agent-skills/agent-skills.md#configuration

but I do remember this discussion and AFAIR we'd decided we don't need path. I'll update the design doc too

Based on further discussions:

It's a little weird to look at now, but the current layout is the safest approach - I can't think of anything else that might be needed under the skills tab (eg, settings etc), but keeping it tabbe-ed future proofs it so keeping it as is

# Option A: Directory containing multiple skill subdirectories
# Each subdirectory must contain a SKILL.md file
- "/var/skills/"

# Option B: Individual skill paths for fine-grained control
# - "/var/skills/openshift-troubleshooting/"
# - "/var/skills/code-review/"
# - "/opt/custom-skills/deployment-guide/"
26 changes: 26 additions & 0 deletions src/models/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1916,6 +1916,26 @@ class AzureEntraIdConfiguration(ConfigurationBase):
)


class SkillsConfiguration(ConfigurationBase):
"""Agent skills configuration.

Specifies paths to skill directories. Skill metadata (name, description)
is read from SKILL.md frontmatter at startup.

Each path can point to either:
- A directory containing a SKILL.md file (single skill)
- A directory containing subdirectories with SKILL.md files (multiple skills)

Paths are validated at startup to ensure they exist and contain valid SKILL.md files.
"""

paths: list[str] = Field(
default_factory=list,
title="Skill paths",
description="Paths to skill directories or directories containing skill subdirectories.",
)
Comment on lines +1932 to +1936
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add validation for individual skills.paths entries.

skills.paths currently accepts blank/whitespace and duplicate values, which can lead to ambiguous or invalid path handling later. Add a field validator to normalize and reject invalid entries.

Suggested patch
 class SkillsConfiguration(ConfigurationBase):
@@
     paths: list[str] = Field(
         default_factory=list,
         title="Skill paths",
         description="Paths to skill directories or directories containing skill subdirectories.",
     )
+
+    `@field_validator`("paths")
+    `@classmethod`
+    def validate_paths(cls, value: list[str]) -> list[str]:
+        """Normalize and validate configured skill paths."""
+        seen: set[str] = set()
+        normalized_paths: list[str] = []
+        for path in value:
+            normalized = path.strip()
+            if not normalized:
+                raise ValueError("Skill paths must not contain empty values")
+            if normalized in seen:
+                raise ValueError(f"Duplicate skill path: '{normalized}'")
+            seen.add(normalized)
+            normalized_paths.append(normalized)
+        return normalized_paths

As per coding guidelines: src/models/**/*.py: “Pydantic models must use @model_validator and @field_validator for validation and complete type annotations for all attributes, avoiding Any type”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/models/config.py` around lines 1932 - 1936, Add validation for the paths
field by adding Pydantic validators for the paths attribute: implement a
`@field_validator`("paths", mode="before", each_item=True) that strips each string
and raises ValueError for blank/whitespace entries, and implement a second
`@field_validator`("paths", mode="after") that deduplicates the list while
preserving order (e.g., using an ordered set pattern) and returns the normalized
list; reference the existing paths: list[str] Field declaration and ensure all
validators are annotated and use Pydantic v2 style `@field_validator` for the
"paths" field.



class Configuration(ConfigurationBase):
"""Global service configuration."""

Expand Down Expand Up @@ -2076,6 +2096,12 @@ class Configuration(ConfigurationBase):
"in rag.inline or rag.tool.",
)

skills: Optional[SkillsConfiguration] = Field(
default=None,
title="Agent skills",
description="Agent skills configuration. Specifies paths to skill directories.",
)

@model_validator(mode="after")
def validate_mcp_auth_headers(self) -> Self:
"""
Expand Down
5 changes: 5 additions & 0 deletions tests/unit/models/config/test_dump_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ def test_dump_configuration(tmp_path: Path) -> None:
},
"splunk": None,
"deployment_environment": "development",
"skills": None,
}


Expand Down Expand Up @@ -596,6 +597,7 @@ def test_dump_configuration_with_quota_limiters(tmp_path: Path) -> None:
},
"splunk": None,
"deployment_environment": "development",
"skills": None,
}


Expand Down Expand Up @@ -839,6 +841,7 @@ def test_dump_configuration_with_quota_limiters_different_values(
},
"splunk": None,
"deployment_environment": "development",
"skills": None,
}


Expand Down Expand Up @@ -1057,6 +1060,7 @@ def test_dump_configuration_byok(tmp_path: Path) -> None:
},
"splunk": None,
"deployment_environment": "development",
"skills": None,
}


Expand Down Expand Up @@ -1260,4 +1264,5 @@ def test_dump_configuration_pg_namespace(tmp_path: Path) -> None:
},
"splunk": None,
"deployment_environment": "development",
"skills": None,
}
47 changes: 47 additions & 0 deletions tests/unit/models/config/test_skills_configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""Unit tests for SkillsConfiguration model."""

# pylint: disable=no-member
# Pydantic Field(default_factory=...) pattern confuses pylint's static analysis

import pytest
from pydantic import ValidationError

from models.config import SkillsConfiguration


class TestSkillsConfiguration:
"""Tests for SkillsConfiguration model."""

def test_empty_paths_list(self) -> None:
"""Test that an explicit empty paths list is allowed."""
config = SkillsConfiguration(paths=[])
assert config.paths == []

def test_no_unknown_fields_allowed(self) -> None:
"""Test that SkillsConfiguration rejects unknown fields."""
with pytest.raises(ValidationError, match="Extra inputs are not permitted"):
SkillsConfiguration(unknown_field="value") # type: ignore[call-arg]

def test_skill_paths(self) -> None:
"""Test configuration with multiple skill paths."""
config = SkillsConfiguration(
paths=[
"/var/skills/openshift-troubleshooting",
"/var/skills/code-review",
"/opt/custom-skills",
]
)
assert len(config.paths) == 3
assert "/var/skills/openshift-troubleshooting" in config.paths
assert "/var/skills/code-review" in config.paths
assert "/opt/custom-skills" in config.paths

def test_mixed_absolute_and_relative_paths(self) -> None:
"""Test that both absolute and relative paths can be mixed."""
config = SkillsConfiguration(
paths=["/var/skills", "./local-skills", "/opt/skills"]
)
assert len(config.paths) == 3
assert "/var/skills" in config.paths
assert "./local-skills" in config.paths
assert "/opt/skills" in config.paths
Comment on lines +39 to +47
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@radofuchs what do you think about this test? From the code's perspective, absolute/relative doesn't matter. It's just "str" at the end of the day - but I added this coz it signals that we'll work with both absolute and relative path - ie this is my attempt at BDD :)

Loading