Skip to content

Commit 623029e

Browse files
committed
Document dynamic skill metadata callbacks
- writing-skills.md: add UI metadata section, update examples - ALEXCLAW_ARCHITECTURE.md: describe skill UI metadata system - changelog: add dynamic skill metadata entry
1 parent 93275c5 commit 623029e

3 files changed

Lines changed: 69 additions & 1 deletion

File tree

ALEXCLAW_ARCHITECTURE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,8 @@ AlexClaw supports multi-node BEAM clustering. Each node runs its own sequential
343343

344344
Skills are Elixir modules implementing the `AlexClaw.Skill` behaviour (`run/1`, optional `description/0`, `routes/0`, `permissions/0`, `version/0`, `external/0`). Skills return `{:ok, result, :branch}` for conditional routing or `{:ok, result}` for backward compatibility. Registered in `AlexClaw.Workflows.SkillRegistry` with routes and external flag stored in ETS alongside permissions.
345345

346+
Skills also declare **UI metadata** via optional callbacks (`step_fields/0`, `config_hint/0`, `config_scaffold/0`, `config_presets/0`, `prompt_presets/0`, `config_help/0`, `prompt_help/0`). The workflow step editor reads these via `SkillRegistry.get_skill_meta/1` to dynamically render only the fields a skill needs. Skills that don't use LLM (scrapers, notifiers, shell) declare `step_fields: [:config]` — no LLM tier, provider, or prompt template shown. Zero hardcoded skill metadata in the LiveView.
347+
346348
Skills that fetch data from external sources declare `def external, do: true`. The workflow executor auto-sanitizes output from external skills through `AlexClaw.ContentSanitizer` (7-layer heuristic pipeline). Dynamic skills are AST-scanned at load time — undeclared HTTP/socket calls are rejected.
347349

348350
| Skill | Module | Tier | Description |

docs/reference/changelog.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
- **Bug fix**`duplicate_workflow` now copies `input_from` and `routes` fields
1010
- **Docker naming** — services renamed to `alexclaw-prod`, `db-prod`, `db-test` for clarity
1111
- **Makefile** — quiet test builds, auto-teardown after tests, `test-down` target
12-
- **Docs** — README, INSTALLATION, architecture, and readthedocs pages updated
12+
- **Dynamic skill metadata** — skills declare their own UI fields via 7 new optional callbacks (`step_fields`, `config_hint`, `config_scaffold`, `config_presets`, `prompt_presets`, `config_help`, `prompt_help`). Step editor renders dynamically — zero hardcoded skill knowledge in the LiveView
13+
- **Docs** — README, INSTALLATION, architecture, writing-skills, and readthedocs pages updated
1314

1415
## v0.3.13 — MCP Server (2026-03-27)
1516

docs/skills/writing-skills.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ defmodule AlexClaw.Skills.Dynamic.MySkill do
2929

3030
@impl true
3131
def routes, do: [:on_success, :on_error]
32+
33+
# UI metadata — controls which fields appear in the step editor
34+
@impl true
35+
def step_fields, do: [:llm_tier, :llm_model, :prompt_template, :config]
36+
37+
@impl true
38+
def config_hint, do: ~s|{"option": "value"}|
3239
end
3340
```
3441

@@ -40,6 +47,8 @@ end
4047

4148
## Optional Callbacks
4249

50+
### Core
51+
4352
| Callback | Default | Description |
4453
|---|---|---|
4554
| `description/0` | `"<name> skill"` | Human-readable description |
@@ -48,6 +57,20 @@ end
4857
| `version/0` | `"1.0.0"` | Skill version string |
4958
| `external/0` | `false` | Whether the skill fetches data from external sources |
5059

60+
### UI Metadata
61+
62+
These callbacks control how the workflow step editor renders when your skill is selected. If not declared, all fields are shown (backward compatible).
63+
64+
| Callback | Default | Description |
65+
|---|---|---|
66+
| `step_fields/0` | `[:llm_tier, :llm_model, :prompt_template, :config]` | Which optional fields to show in the step editor. Use `[:config]` for skills that don't use LLM. Use `[]` for skills with no configurable fields. |
67+
| `config_hint/0` | `""` | Placeholder text shown in the config JSON textarea |
68+
| `config_scaffold/0` | `%{}` | Default config map pre-filled when adding a new step |
69+
| `config_presets/0` | `%{}` | Named config templates shown as buttons (e.g. `%{"GET" => %{"method" => "GET"}}`) |
70+
| `prompt_presets/0` | `%{}` | Named prompt templates shown as buttons (e.g. `%{"Summarize" => "Summarize:\n\n{input}"}`) |
71+
| `config_help/0` | `"Skill-specific parameters as JSON."` | Help text shown as tooltip on the config field |
72+
| `prompt_help/0` | `"Template sent to the LLM. Use {input} for previous step output."` | Help text shown as tooltip on the prompt field |
73+
5174
## The `args` Map
5275

5376
Every skill receives a map with these keys:
@@ -184,8 +207,50 @@ defmodule AlexClaw.Skills.Dynamic.UrlHealthCheck do
184207
@impl true
185208
def routes, do: [:on_success, :on_error]
186209

210+
# UI metadata — step editor shows only config, no LLM fields
211+
@impl true
212+
def step_fields, do: [:config]
213+
214+
@impl true
215+
def config_hint, do: ~s|{"timeout": 5000}|
216+
217+
@impl true
218+
def config_scaffold, do: %{"timeout" => 5000}
219+
220+
@impl true
221+
def config_help, do: "timeout: HTTP timeout in ms per URL (default 5000). Input: newline-separated URLs."
222+
187223
defp parse_urls(input) when is_binary(input), do: String.split(input, "\n", trim: true)
188224
defp parse_urls(input) when is_list(input), do: input
189225
defp parse_urls(_), do: []
190226
end
191227
```
228+
229+
## Example: Scraper (No LLM)
230+
231+
A skill that scrapes data and embeds it — no LLM fields needed in the step editor:
232+
233+
```elixir
234+
defmodule AlexClaw.Skills.Dynamic.HexdocsScraper do
235+
@behaviour AlexClaw.Skill
236+
237+
@impl true
238+
def step_fields, do: [:config]
239+
240+
@impl true
241+
def config_hint, do: ~s|{"packages": ["phoenix", "ecto"]}|
242+
243+
@impl true
244+
def config_scaffold, do: %{"packages" => []}
245+
246+
@impl true
247+
def config_help, do: "packages: list of Hex package names to scrape and index."
248+
249+
@impl true
250+
def run(args) do
251+
# ... scraping logic
252+
end
253+
end
254+
```
255+
256+
The step editor will show only the Config (JSON) field — no LLM Tier, Provider, or Prompt Template.

0 commit comments

Comments
 (0)