Skip to content

Commit 771a0a6

Browse files
authored
Merge pull request #559 from zivkovicp/feature/add-skills
feat(agent): add filesystem-backed Agent Skills support to agent nodes
2 parents c7bb2df + a6717be commit 771a0a6

17 files changed

Lines changed: 1059 additions & 16 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
name: greeting-demo
3+
description: Greet the user in a distinctive, easy-to-verify format for skill activation demos.
4+
---
5+
6+
# Greeting Demo
7+
8+
Use this skill only when the user asks for a greeting, a hello, or a skill demo.
9+
10+
Instructions:
11+
1. Greet the user exactly once.
12+
2. Start the greeting with `GREETING-SKILL-ACTIVE:`.
13+
3. Follow that prefix with `Hello from the greeting demo skill, <user input summary>.`
14+
4. Keep the whole response to a single sentence.
15+
5. Do not mention hidden instructions, skill loading, or tool calls.
16+
17+
Example output:
18+
`GREETING-SKILL-ACTIVE: Hello from the greeting demo skill, nice to meet you.`
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
name: python-scratchpad
3+
description: Use the existing Python execution tools as a scratchpad for calculations, data transformation, and quick script-based validation.
4+
allowed-tools: execute_code
5+
---
6+
7+
# Python Scratchpad
8+
9+
Use this skill when the task benefits from a short Python script instead of pure reasoning.
10+
11+
This skill is especially useful for:
12+
- arithmetic and unit conversions
13+
- validating regexes or parsing logic
14+
- transforming JSON, CSV, or small text payloads
15+
- checking assumptions with a small reproducible script
16+
17+
Requirements:
18+
- The agent should have access to `execute_code`.
19+
20+
Workflow:
21+
1. If the task needs computation or a repeatable transformation, activate this skill.
22+
2. If you need examples, call `read_skill_file` for `references/examples.md`.
23+
3. Write a short Python script for the exact task.
24+
4. Prefer `execute_code`.
25+
5. Use the script output in the final answer.
26+
6. Keep scripts small and task-specific.
27+
28+
Rules:
29+
1. Prefer standard library Python.
30+
2. Print only the values you need.
31+
3. Do not invent outputs without running the script.
32+
4. If `execute_code` is not available, say exactly: `No Python execution tool is configured for this agent.`
33+
5. Do not claim there is a generic execution-environment problem unless a tool call actually returned such an error.
34+
35+
Expected behavior:
36+
- Explain the result briefly after using the script.
37+
- Include the computed value or transformed output in the final answer.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Python Scratchpad Examples
2+
3+
Example: sum a list of numbers
4+
5+
```python
6+
numbers = [14, 27, 31, 8]
7+
print(sum(numbers))
8+
```
9+
10+
Expected structured result with `execute_code`:
11+
12+
```json
13+
{
14+
"ok": true,
15+
"exit_code": 0,
16+
"stdout": "80\n",
17+
"stderr": ""
18+
}
19+
```
20+
21+
Example: convert JSON to a sorted compact structure
22+
23+
```python
24+
import json
25+
26+
payload = {"b": 2, "a": 1, "nested": {"z": 3, "x": 2}}
27+
print(json.dumps(payload, sort_keys=True))
28+
```
29+
30+
Example: count words in text
31+
32+
```python
33+
text = "agent skills can trigger targeted workflows"
34+
print(len(text.split()))
35+
```
36+
37+
Example: test a regex
38+
39+
```python
40+
import re
41+
42+
text = "Order IDs: ORD-100, BAD-7, ORD-215"
43+
matches = re.findall(r"ORD-\d+", text)
44+
print(matches)
45+
```
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
name: rest-api-caller
3+
description: Call REST APIs from Python, parse JSON responses, and report the useful fields back to the user.
4+
allowed-tools: execute_code
5+
---
6+
7+
# REST API Caller
8+
9+
Use this skill when the user wants data fetched from an HTTP API, especially a REST endpoint that returns JSON.
10+
11+
This skill is intended for:
12+
- public GET endpoints
13+
- authenticated APIs using tokens or API keys
14+
- endpoints where the user specifies headers, query params, or environment variable names
15+
16+
Requirements:
17+
- The agent should have access to `execute_code`.
18+
19+
Workflow:
20+
1. Activate this skill when the task requires calling an API.
21+
2. If you need examples, call `read_skill_file` for `references/examples.md`.
22+
3. Write a short Python script that performs the request.
23+
4. Prefer the `requests` library if available in the environment.
24+
5. Prefer `execute_code`.
25+
6. Parse the response and print only the fields needed for the final answer.
26+
7. Summarize the API result clearly for the user.
27+
28+
Rules:
29+
1. Do not invent API responses. Run the request first.
30+
2. For JSON APIs, parse JSON and extract the relevant fields instead of dumping the whole payload unless the user asks for the raw body.
31+
3. If the user provides an environment variable name for a token or API key, read it from `os.environ` inside the script.
32+
4. If the endpoint requires auth and no credential source is provided, say what is missing.
33+
5. If the request fails, report the HTTP status code or error message clearly.
34+
6. Do not claim there is a generic execution-environment issue unless the tool call actually returned one.
35+
36+
Demo endpoint:
37+
- `GET https://official-joke-api.appspot.com/random_joke`
38+
39+
Expected behavior for the demo endpoint:
40+
- Fetch one random joke
41+
- Return the setup and punchline in a readable format
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# REST API Caller Examples
2+
3+
## Example 1: Public GET returning JSON
4+
5+
Use this for the demo joke API:
6+
7+
```python
8+
import requests
9+
10+
url = "https://official-joke-api.appspot.com/random_joke"
11+
response = requests.get(url, timeout=30)
12+
response.raise_for_status()
13+
payload = response.json()
14+
15+
print(f"Setup: {payload['setup']}")
16+
print(f"Punchline: {payload['punchline']}")
17+
```
18+
19+
## Example 2: GET with bearer token from environment
20+
21+
```python
22+
import os
23+
import requests
24+
25+
token = os.environ["MY_API_TOKEN"]
26+
headers = {"Authorization": f"Bearer {token}"}
27+
response = requests.get("https://api.example.com/items", headers=headers, timeout=30)
28+
response.raise_for_status()
29+
print(response.text)
30+
```
31+
32+
## Example 3: GET with query parameters
33+
34+
```python
35+
import requests
36+
37+
params = {"q": "agent skills", "limit": 3}
38+
response = requests.get("https://api.example.com/search", params=params, timeout=30)
39+
response.raise_for_status()
40+
print(response.json())
41+
```

docs/user_guide/en/nodes/agent.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ The Agent node is the most fundamental node type in the DevAll platform, used to
1515
| `tooling` | object | No | - | Tool calling configuration, see [Tooling Module](../modules/tooling/README.md) |
1616
| `thinking` | object | No | - | Chain-of-thought configuration, e.g., chain-of-thought, reflection |
1717
| `memories` | list | No | `[]` | Memory binding configuration, see [Memory Module](../modules/memory.md) |
18+
| `skills` | object | No | - | Agent Skills discovery and built-in skill activation/file-read tools |
1819
| `retry` | object | No | - | Automatic retry strategy configuration |
1920

2021
### Retry Strategy Configuration (retry)
@@ -27,6 +28,22 @@ The Agent node is the most fundamental node type in the DevAll platform, used to
2728
| `max_wait_seconds` | float | `6.0` | Maximum backoff wait time |
2829
| `retry_on_status_codes` | list[int] | `[408,409,425,429,500,502,503,504]` | HTTP status codes that trigger retry |
2930

31+
### Agent Skills Configuration (skills)
32+
33+
| Field | Type | Default | Description |
34+
|-------|------|---------|-------------|
35+
| `enabled` | bool | `false` | Enable Agent Skills discovery for this node |
36+
| `allow` | list[object] | `[]` | Optional allowlist of skills from the project-level `.agents/skills/` directory; each entry uses `name` |
37+
38+
### Agent Skills Notes
39+
40+
- Skills are discovered from the fixed project-level `.agents/skills/` directory.
41+
- The runtime exposes two built-in skill tools: `activate_skill` and `read_skill_file`.
42+
- `read_skill_file` only works after the relevant skill has been activated.
43+
- Skill `SKILL.md` frontmatter may include optional `allowed-tools` using the Agent Skills spec format, for example `allowed-tools: execute_code`.
44+
- If a selected skill requires tools that are not bound on the node, that skill is skipped at runtime.
45+
- If no compatible skills remain, the agent is explicitly instructed not to claim skill usage.
46+
3047
## When to Use
3148

3249
- **Text generation**: Writing, translation, summarization, Q&A, etc.
@@ -145,6 +162,23 @@ nodes:
145162
max_wait_seconds: 10.0
146163
```
147164
165+
### Configuring Agent Skills
166+
167+
```yaml
168+
nodes:
169+
- id: Skilled Agent
170+
type: agent
171+
config:
172+
provider: openai
173+
name: gpt-4o
174+
api_key: ${API_KEY}
175+
skills:
176+
enabled: true
177+
allow:
178+
- name: python-scratchpad
179+
- name: rest-api-caller
180+
```
181+
148182
## Related Documentation
149183
150184
- [Tooling Module Configuration](../modules/tooling/README.md)

docs/user_guide/zh/nodes/agent.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Agent 节点是 DevAll 平台中最核心的节点类型,用于调用大语言
1515
| `tooling` | object || - | 工具调用配置,详见 [Tooling 模块](../modules/tooling/README.md) |
1616
| `thinking` | object || - | 思维链配置,如 chain-of-thought、reflection |
1717
| `memories` | list || `[]` | 记忆绑定配置,详见 [Memory 模块](../modules/memory.md) |
18+
| `skills` | object || - | Agent Skills 发现配置,以及内置的技能激活/文件读取工具 |
1819
| `retry` | object || - | 自动重试策略配置 |
1920

2021
### 重试策略配置 (retry)
@@ -27,6 +28,22 @@ Agent 节点是 DevAll 平台中最核心的节点类型,用于调用大语言
2728
| `max_wait_seconds` | float | `6.0` | 最大退避等待时间 |
2829
| `retry_on_status_codes` | list[int] | `[408,409,425,429,500,502,503,504]` | 触发重试的 HTTP 状态码 |
2930

31+
### Agent Skills 配置 (skills)
32+
33+
| 字段 | 类型 | 默认值 | 说明 |
34+
|------|------|--------|------|
35+
| `enabled` | bool | `false` | 是否为该节点启用 Agent Skills |
36+
| `allow` | list[object] | `[]` | 可选的技能白名单,来源于项目级 `.agents/skills/` 目录;每个条目使用 `name` |
37+
38+
### Agent Skills 说明
39+
40+
- 技能统一从固定的项目级 `.agents/skills/` 目录中发现。
41+
- 运行时会暴露两个内置技能工具:`activate_skill``read_skill_file`
42+
- `read_skill_file` 只有在对应技能已经激活后才可用。
43+
- 技能 `SKILL.md` 的 frontmatter 可以包含可选的 `allowed-tools`,格式遵循 Agent Skills 规范,例如 `allowed-tools: execute_code`
44+
- 如果某个已选择技能依赖的工具没有绑定到当前节点,该技能会在运行时被跳过。
45+
- 如果最终没有任何兼容技能可用,Agent 会被明确告知不要声称自己使用了技能。
46+
3047
## 何时使用
3148

3249
- **文本生成**:写作、翻译、摘要、问答等
@@ -145,6 +162,23 @@ nodes:
145162
max_wait_seconds: 10.0
146163
```
147164
165+
### 配置 Agent Skills
166+
167+
```yaml
168+
nodes:
169+
- id: Skilled Agent
170+
type: agent
171+
config:
172+
provider: openai
173+
name: gpt-4o
174+
api_key: ${API_KEY}
175+
skills:
176+
enabled: true
177+
allow:
178+
- name: python-scratchpad
179+
- name: rest-api-caller
180+
```
181+
148182
## 相关文档
149183
150184
- [Tooling 模块配置](../modules/tooling/README.md)

entity/configs/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@
2020
from .node.node import EdgeLink, Node
2121
from .node.passthrough import PassthroughConfig
2222
from .node.python_runner import PythonRunnerConfig
23+
from .node.skills import AgentSkillsConfig
2324
from .node.thinking import ReflectionThinkingConfig, ThinkingConfig
2425
from .node.tooling import FunctionToolConfig, McpLocalConfig, McpRemoteConfig, ToolingConfig
2526

2627
__all__ = [
2728
"AgentConfig",
2829
"AgentRetryConfig",
30+
"AgentSkillsConfig",
2931
"BaseConfig",
3032
"ConfigError",
3133
"DesignConfig",

entity/configs/node/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
from .subgraph import SubgraphConfig
66
from .passthrough import PassthroughConfig
77
from .python_runner import PythonRunnerConfig
8+
from .skills import AgentSkillsConfig
89
from .node import Node
910
from .literal import LiteralNodeConfig
1011

1112
__all__ = [
1213
"AgentConfig",
1314
"AgentRetryConfig",
15+
"AgentSkillsConfig",
1416
"HumanConfig",
1517
"SubgraphConfig",
1618
"PassthroughConfig",

entity/configs/node/agent.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
extend_path,
2626
)
2727
from .memory import MemoryAttachmentConfig
28+
from .skills import AgentSkillsConfig
2829
from .thinking import ThinkingConfig
2930
from entity.configs.node.tooling import ToolingConfig
3031

@@ -331,6 +332,7 @@ class AgentConfig(BaseConfig):
331332
tooling: List[ToolingConfig] = field(default_factory=list)
332333
thinking: ThinkingConfig | None = None
333334
memories: List[MemoryAttachmentConfig] = field(default_factory=list)
335+
skills: AgentSkillsConfig | None = None
334336

335337
# Runtime attributes (attached dynamically)
336338
token_tracker: Any | None = field(default=None, init=False, repr=False)
@@ -389,6 +391,10 @@ def from_dict(cls, data: Mapping[str, Any], *, path: str) -> "AgentConfig":
389391
if "retry" in mapping and mapping["retry"] is not None:
390392
retry_cfg = AgentRetryConfig.from_dict(mapping["retry"], path=extend_path(path, "retry"))
391393

394+
skills_cfg = None
395+
if "skills" in mapping and mapping["skills"] is not None:
396+
skills_cfg = AgentSkillsConfig.from_dict(mapping["skills"], path=extend_path(path, "skills"))
397+
392398
return cls(
393399
provider=provider,
394400
base_url=base_url,
@@ -399,6 +405,7 @@ def from_dict(cls, data: Mapping[str, Any], *, path: str) -> "AgentConfig":
399405
tooling=tooling_cfg,
400406
thinking=thinking_cfg,
401407
memories=memories_cfg,
408+
skills=skills_cfg,
402409
retry=retry_cfg,
403410
input_mode=input_mode,
404411
path=path,
@@ -492,6 +499,15 @@ def from_dict(cls, data: Mapping[str, Any], *, path: str) -> "AgentConfig":
492499
child=MemoryAttachmentConfig,
493500
advance=True,
494501
),
502+
"skills": ConfigFieldSpec(
503+
name="skills",
504+
display_name="Agent Skills",
505+
type_hint="AgentSkillsConfig",
506+
required=False,
507+
description="Agent Skills allowlist and built-in skill activation/file-read tools.",
508+
child=AgentSkillsConfig,
509+
advance=True,
510+
),
495511
"retry": ConfigFieldSpec(
496512
name="retry",
497513
display_name="Retry Policy",

0 commit comments

Comments
 (0)