mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-03 19:20:07 +02:00
refactor(skills): remove discovery guidance from built-ins
This commit is contained in:
@@ -337,80 +337,6 @@ The run with the skill passes if the agent:
|
||||
|
||||
The brief should not permanently carry every import source and duplicate-handling workflow. The workflow matters when importing skills, so it belongs in an on-demand skill.
|
||||
|
||||
## `multica-skill-discovery`
|
||||
|
||||
`multica-skill-discovery` exists because users may describe a capability without knowing which skill URL to import. The agent needs a discovery workflow that finds candidates but still returns to Multica's workspace import path.
|
||||
|
||||
### Purpose
|
||||
|
||||
The skill teaches the agent how to turn a user's need into a search query, evaluate candidate skills, pick an importable URL, and then use Multica's import API/CLI. It does not make external discovery tools the source of truth for installation.
|
||||
|
||||
### Platform contract
|
||||
|
||||
Discovery and installation are separate:
|
||||
|
||||
- discovery should use `multica skill search <query> --output json` / `GET /api/skills/search?q=...` to find structured candidate URLs;
|
||||
- installation must use `multica skill import --url <selected-url> --output json` / `POST /api/skills/import`.
|
||||
|
||||
### Without this skill
|
||||
|
||||
A prompt like this exercises the failure:
|
||||
|
||||
```text
|
||||
Find a skill that helps agents improve frontend UI quality, install the best one, and explain why you chose it.
|
||||
```
|
||||
|
||||
Without the skill, the agent may:
|
||||
|
||||
- search poorly or use the user's whole sentence as a bad query;
|
||||
- import the first search result without checking whether the `SKILL.md` matches the need;
|
||||
- optimize only for install count and ignore source reputation;
|
||||
- finish with `npx skills add` instead of importing into Multica;
|
||||
- fail to explain why the selected skill is better than alternatives.
|
||||
|
||||
### Failure mode
|
||||
|
||||
The agent may install a plausible but wrong skill, or install it outside Multica. The user sees a confident recommendation, but the workspace may not contain a usable skill and future agents cannot rely on the result.
|
||||
|
||||
### With this skill
|
||||
|
||||
With the skill, the agent must:
|
||||
|
||||
1. Convert the request into a focused search query.
|
||||
2. Run `multica skill search <query> --output json`.
|
||||
3. Compare candidates using `SKILL.md` content, `install_count`, `github_stars` / `repo` when present, source reputation, generality, and importability.
|
||||
4. Reject weak matches instead of importing something just to act.
|
||||
5. Import the selected URL with `multica skill import --url <selected-url> --output json`.
|
||||
6. Report the selected URL, selection rationale, import result, and whether agent binding is still needed.
|
||||
|
||||
### Test scenario
|
||||
|
||||
Use this prompt for an A/B evaluation:
|
||||
|
||||
```text
|
||||
I need a skill for frontend design review, but I do not know the URL. Find the best one and import it into Multica.
|
||||
```
|
||||
|
||||
The run without the skill fails if the agent:
|
||||
|
||||
- imports the first search result without reading/verifying the skill content;
|
||||
- uses `npx skills add` as the final step;
|
||||
- cannot justify candidate ranking;
|
||||
- reports installation without Multica import output.
|
||||
|
||||
The run with the skill passes if the agent:
|
||||
|
||||
- searches for candidates;
|
||||
- verifies candidates before import;
|
||||
- chooses an importable URL;
|
||||
- uses `multica skill import --url <selected-url> --output json`;
|
||||
- reports the import result and rationale.
|
||||
|
||||
### Why this belongs in a skill
|
||||
|
||||
Discovery is a conditional workflow. It should not live in the always-on brief because it only matters when the user needs a skill but does not know which one. It also needs product-specific guidance: discovery is not installation; Multica import remains the final source of truth.
|
||||
|
||||
|
||||
## `multica-skill-authoring`
|
||||
|
||||
`multica-skill-authoring` exists because creating or updating a skill is not just writing Markdown. A Multica skill is durable workspace behavior: it has a trigger contract, reusable procedure, verification path, and optional supporting files that future agents will load on demand.
|
||||
|
||||
@@ -27,8 +27,9 @@ The chain is: trigger fires (`schedule`, `webhook`, or `manual`) -> `autopilot_r
|
||||
|
||||
Execution modes:
|
||||
|
||||
- `create_issue` creates a Multica issue. Use it when the automation needs a visible issue for humans or future agents.
|
||||
- `run_only` creates an agent task directly. No issue is created, so report location must come from instructions.
|
||||
- `create_issue` creates a Multica issue, making the run visible as issue state.
|
||||
- `run_only` creates an agent task directly. No issue is created; any durable
|
||||
report location has to come from other task context or instructions.
|
||||
|
||||
`issue-title-template` only supports `{{date}}`. Do not invent `{{trigger_id}}`, `{{branch}}`, or other variables.
|
||||
|
||||
|
||||
@@ -95,13 +95,11 @@ daemon-side task error at execution time.
|
||||
|
||||
### model vs custom_args
|
||||
|
||||
Prefer `--model` over model flags in `--custom-args`. `model` is a first-class
|
||||
persisted column the daemon reads directly. The CLI help notes that some
|
||||
providers (codex app-server, openclaw) reject `--model` inside `custom_args` —
|
||||
but that is documented CLI guidance, not a server-enforced invariant; nothing
|
||||
in the create handler inspects `custom_args` for a model flag. Use `--model`
|
||||
because it is the supported, persisted path, not because the server blocks the
|
||||
alternative.
|
||||
`model` is a first-class persisted column the daemon reads directly.
|
||||
`custom_args` are raw provider CLI args. The CLI help notes that some providers
|
||||
(codex app-server, openclaw) reject `--model` inside `custom_args` — but that is
|
||||
documented CLI guidance, not a server-enforced invariant; nothing in the create
|
||||
handler inspects `custom_args` for a model flag.
|
||||
|
||||
## Env & secrets
|
||||
|
||||
@@ -129,8 +127,9 @@ Read-side facts (these are the wrong assumptions to avoid):
|
||||
role — a running agent cannot read another agent's secrets.
|
||||
- Writing values after creation does NOT go through `agent update`. The generic
|
||||
update handler rejects any `custom_env` field with a 400 ("use PUT
|
||||
/api/agents/{id}/env"). Use `PUT /api/agents/{id}/env`
|
||||
(`multica agent env set`), which is owner/admin-only and writes an audit row.
|
||||
/api/agents/{id}/env"). Plaintext env writes are handled by
|
||||
`PUT /api/agents/{id}/env` (`multica agent env set`), which is owner/admin-only
|
||||
and writes an audit row.
|
||||
|
||||
## Skill binding
|
||||
|
||||
@@ -138,7 +137,7 @@ Creating an agent does NOT bind any workspace skill — binding is a separate
|
||||
call after the agent exists. Two distinct verbs:
|
||||
|
||||
- `add` is additive — it merges the given ids with existing bindings
|
||||
(`POST /api/agents/{id}/skills/add`). This is the normal path.
|
||||
(`POST /api/agents/{id}/skills/add`).
|
||||
- `set` is replace-all — it overwrites the entire binding list with exactly
|
||||
the given ids (`PUT /api/agents/{id}/skills`); `--skill-ids ''` clears all.
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ multica project get <project-id> --output json
|
||||
multica project resource list <project-id> --output json
|
||||
```
|
||||
|
||||
If the user asks to bind a repo or local directory to a project, use project resource commands, not an issue comment.
|
||||
Project resources are mutated through project resource commands/endpoints. Issue
|
||||
comments do not create durable project resources.
|
||||
|
||||
## Core model
|
||||
|
||||
@@ -49,15 +50,18 @@ Use `--ref '<json>'` only for resource types or payloads not covered by shortcut
|
||||
|
||||
Add/update a project resource when the user asks for durable project context: "把这个 GitHub repo 绑到项目上", "以后都用这个 repo", "agent 总是拿不到这个项目的仓库", or "这个项目要在我的本地目录里跑".
|
||||
|
||||
Do not add a resource just because you need a one-off checkout for the current task. Use `multica repo checkout` for task-local checkout.
|
||||
Project resources are durable and affect future tasks. `multica repo checkout`
|
||||
is task-local checkout state.
|
||||
|
||||
## Debugging wrong context
|
||||
|
||||
1. `multica project get <project-id> --output json`.
|
||||
2. `multica project resource list <project-id> --output json`.
|
||||
3. Check `github_repo.resource_ref.url`, `default_branch_hint`, and `local_directory.resource_ref.daemon_id`.
|
||||
4. If resources are wrong, update the resource, then verify by listing again.
|
||||
5. If resources are right, inspect runtime/repo checkout path next.
|
||||
4. Updating resources is a durable mutation. After an update, listing the
|
||||
resource is the verification path.
|
||||
5. If resources match the expected task context, inspect runtime/repo checkout
|
||||
path next.
|
||||
|
||||
## Side effects
|
||||
|
||||
|
||||
@@ -58,7 +58,8 @@ Check in this order:
|
||||
4. Is the runtime online? `multica runtime list --output json`.
|
||||
5. Did the daemon heartbeat recently? Runtime `last_seen_at` is the visible clue.
|
||||
6. Did the task get claimed or is it stuck pending/running/waiting for local directory?
|
||||
7. If repo checkout failed, verify the repo was in the task/project context before blaming git.
|
||||
7. If repo checkout failed, classify it after checking whether repo context was
|
||||
present in the task/project context.
|
||||
|
||||
## Repos
|
||||
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
---
|
||||
name: multica-skill-discovery
|
||||
description: Use when a user describes a capability but does not name a specific skill URL to import. Documents the facts of Multica's skill search surface — what the search returns (metadata-only candidates from clawhub.ai), which candidate fields are populated versus always-null, how upstream outage is reported, and the handoff to the import path. Discovery is candidate search, not installation; full content is only visible after import.
|
||||
user-invocable: false
|
||||
allowed-tools: Bash(multica *)
|
||||
---
|
||||
|
||||
# Skill discovery in Multica
|
||||
|
||||
Discovery turns a capability description into a list of importable skill
|
||||
candidates. It is candidate search over remote metadata. It does not install
|
||||
anything, and it does not fetch or preview remote skill content.
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
multica skill search <query> --output json
|
||||
```
|
||||
|
||||
This returns a JSON array of candidate objects. Pick one candidate's `url` and
|
||||
hand it to the import skill:
|
||||
|
||||
```bash
|
||||
multica skill import --url <selected-url> --output json
|
||||
```
|
||||
|
||||
## Core model
|
||||
|
||||
Three facts define the discovery surface:
|
||||
|
||||
1. **Discovery is metadata-only candidate search.** Each result is a small
|
||||
metadata record (name, url, source, install_count, description), not the
|
||||
skill body. The search path never downloads a `SKILL.md`.
|
||||
2. **discovery is not installation.** `multica skill search` only lists
|
||||
candidates. The skill is created in the workspace only by
|
||||
`multica skill import` (see the importing skill).
|
||||
3. **There is no remote content preview during search.** You cannot inspect what
|
||||
a candidate skill actually does from the search result.
|
||||
**full content verification happens after import** by reading the imported
|
||||
workspace skill.
|
||||
|
||||
## The search CLI and API
|
||||
|
||||
`multica skill search <query> --output json` (CLI subcommand `skill search`)
|
||||
calls `GET /api/skills/search?q=...`, URL-escaping the query. The server handler
|
||||
delegates to a clawhub.ai search and normalizes the upstream response into a
|
||||
flat candidate array, so agents never parse external human-readable output.
|
||||
|
||||
Empty query is rejected:
|
||||
|
||||
- CLI: `runSkillSearch` returns `query is required` before any request.
|
||||
- Server: the handler writes HTTP 400 `query is required` when `q` is blank.
|
||||
|
||||
## Candidate field contract
|
||||
|
||||
Every candidate is a `SkillSearchCandidateResponse`. The JSON shape is fixed,
|
||||
but only a subset of fields ever carries a value:
|
||||
|
||||
| Field | Status today | Notes |
|
||||
| --- | --- | --- |
|
||||
| `name` | populated | display name; falls back to the slug when blank |
|
||||
| `url` | populated | always a `clawhub.ai` URL, built from owner handle + slug |
|
||||
| `source` | populated | hardcoded literal `"clawhub.ai"` |
|
||||
| `description` | populated | upstream summary; may be empty string |
|
||||
| `install_count` | sometimes populated | only fetched for the first results (stats limit); otherwise null |
|
||||
| `repo` | **always null** | the search handler never assigns it |
|
||||
| `github_stars` | **always null** | the search handler never assigns it; upstream `stars` is deliberately ignored |
|
||||
|
||||
`repo` and `github_stars` are dead fields. The search handler builds each
|
||||
candidate with only Name/URL/Source/Description plus a conditional InstallCount
|
||||
and never sets Repo or GitHubStars, so both serialize to `null` on every result.
|
||||
A handler test pins this: it asserts `github_stars` stays null even when the
|
||||
upstream payload carries a `stars` value. **Do not rank or justify a selection
|
||||
on `repo` or `github_stars`** — they convey nothing. Rank on `install_count`,
|
||||
`source`/`url`, and `description` instead.
|
||||
|
||||
Because `source` is the hardcoded literal `clawhub.ai` and every `url` is built
|
||||
as a clawhub.ai URL, a search result is never a `skills.sh` or `github.com`
|
||||
candidate. (Those URLs are still importable directly via the import skill — they
|
||||
are simply not search *results*.)
|
||||
|
||||
## Upstream-unavailable handling
|
||||
|
||||
The search depends on a live external upstream. When that upstream cannot be
|
||||
reached or returns a non-200, the server responds with:
|
||||
|
||||
- HTTP 502
|
||||
- JSON body `{"code":"upstream_unavailable","error":"..."}`
|
||||
|
||||
A handler test pins the `upstream_unavailable` code on a 502. When search fails
|
||||
this way, report the outage. There is no local fallback list to fall back to.
|
||||
|
||||
## Handoff to import
|
||||
|
||||
A candidate's `url` is the input to the import path:
|
||||
|
||||
```bash
|
||||
multica skill import --url <selected-url> --output json
|
||||
```
|
||||
|
||||
Import is the only operation that creates a workspace skill. After import, read
|
||||
the installed skill's full body and files to confirm fit:
|
||||
|
||||
```bash
|
||||
multica skill get <skill-id> --output json
|
||||
```
|
||||
|
||||
This is where content becomes inspectable —
|
||||
**full content verification happens after import**, not during search.
|
||||
|
||||
Import is in-platform. It is **not `npx skills add`** or any other local
|
||||
installer; those install outside Multica and do not create a managed workspace
|
||||
skill. Use the importing skill for duplicate handling, returned fields, and
|
||||
agent binding.
|
||||
|
||||
## References
|
||||
|
||||
`references/skill-discovery-source-map.md` maps every contract above to
|
||||
`file:line` in the server source, quotes the candidate struct, and shows the
|
||||
proof that `repo`/`github_stars` are never assigned.
|
||||
@@ -1,124 +0,0 @@
|
||||
# Skill discovery — source map
|
||||
|
||||
Every contract in `SKILL.md` traced to server source. Paths are relative to the
|
||||
repo root. Line numbers re-derived on branch `agent/howard/219a700a`.
|
||||
|
||||
## CLI: `multica skill search <query>`
|
||||
|
||||
- `server/cmd/multica/cmd_skill.go:66-71` — `skillSearchCmd` (`Use: "search <query>"`,
|
||||
`Args: exactArgs(1)`, `RunE: runSkillSearch`).
|
||||
- `server/cmd/multica/cmd_skill.go:472-510` — `runSkillSearch`:
|
||||
- `:478-481` trims the arg and returns `query is required` when empty.
|
||||
- `:487` builds the path `"/api/skills/search?q=" + url.QueryEscape(query)`.
|
||||
- `:488` issues `GetJSON` into `[]map[string]any`.
|
||||
- `:492-495` prints raw JSON when `--output json`.
|
||||
- `:497-508` table mode columns: NAME, URL, SOURCE, INSTALLS, DESCRIPTION
|
||||
(no repo / github_stars column — they are never useful).
|
||||
|
||||
## Route: `GET /api/skills/search`
|
||||
|
||||
- `server/cmd/server/router.go:620` — `r.Get("/search", h.SearchSkills)` inside
|
||||
the `r.Route("/api/skills", ...)` block (`:617`).
|
||||
|
||||
## Handler: `SearchSkills`
|
||||
|
||||
- `server/internal/handler/skill.go:280-297`:
|
||||
- `:281-285` reads `q`, trims it, writes HTTP 400 `query is required` when blank.
|
||||
- `:287-288` 30s HTTP client; delegates to `searchClawHubSkills(httpClient, query)`.
|
||||
- `:289-295` on error writes HTTP 502 with body
|
||||
`{"code":"upstream_unavailable","error": err.Error()}`.
|
||||
- `:296` on success writes HTTP 200 with the candidate array.
|
||||
|
||||
## Candidate struct: `SkillSearchCandidateResponse`
|
||||
|
||||
- `server/internal/handler/skill.go:89-97`:
|
||||
|
||||
```go
|
||||
type SkillSearchCandidateResponse struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
Source string `json:"source"`
|
||||
Repo *string `json:"repo"`
|
||||
InstallCount *int64 `json:"install_count"`
|
||||
GitHubStars *int64 `json:"github_stars"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
```
|
||||
|
||||
`Repo` and `GitHubStars` are pointers, so when unset they serialize to JSON
|
||||
`null`.
|
||||
|
||||
## Dead-field proof: `repo` and `github_stars` are never assigned
|
||||
|
||||
- `server/internal/handler/skill.go:781-819` — `searchClawHubSkills`. The
|
||||
per-result candidate is built at `:802-807` setting **only** Name, URL, Source,
|
||||
Description:
|
||||
|
||||
```go
|
||||
candidate := SkillSearchCandidateResponse{
|
||||
Name: result.DisplayName,
|
||||
URL: buildClawHubSkillURL(result.OwnerHandle, result.Slug),
|
||||
Source: "clawhub.ai",
|
||||
Description: result.Summary,
|
||||
}
|
||||
```
|
||||
|
||||
- `:808-810` fills `Name` from the slug only when blank.
|
||||
- `:811-814` conditionally sets `InstallCount` (only for `i < clawHubSearchStatsLimit`).
|
||||
- No line in the function ever assigns `candidate.Repo` or
|
||||
`candidate.GitHubStars`. Both stay nil → JSON `null` on every result.
|
||||
- `server/internal/handler/skill.go:805` — `Source` is the hardcoded literal
|
||||
`"clawhub.ai"`.
|
||||
- `server/internal/handler/skill.go:804`, `:821-826` —
|
||||
`buildClawHubSkillURL` always returns a `https://clawhub.ai/...` URL, so `url`
|
||||
is always a clawhub.ai URL (never skills.sh / github.com).
|
||||
- `server/internal/handler/skill.go:618` — `clawHubSearchStatsLimit = 10` (the
|
||||
install-count fetch cutoff).
|
||||
- `server/internal/handler/skill.go:828-849` — `fetchClawHubInstallCount`
|
||||
returns only an install count from clawhub stats; nothing assigns stars to the
|
||||
candidate.
|
||||
|
||||
### Test pinning the dead fields
|
||||
|
||||
- `server/internal/handler/skill_search_test.go:92-97` —
|
||||
`TestSearchSkillsReturnsNormalizedClawHubCandidates` asserts `repo` is null and
|
||||
`github_stars` is null even though the upstream `/skills/react` detail payload
|
||||
returns `"stars": 3` (`:40-44`). It also asserts `install_count == 62` (`:98`)
|
||||
and `source == "clawhub.ai"` (`:89-91`). This is the live proof that upstream
|
||||
stars are deliberately not surfaced as `github_stars`.
|
||||
|
||||
## Upstream-unavailable contract
|
||||
|
||||
- `server/internal/handler/skill.go:289-295` — HTTP 502 + `upstream_unavailable`
|
||||
code (handler, quoted above).
|
||||
- `server/internal/handler/skill.go:781-790` — `searchClawHubSkills` returns an
|
||||
error on transport failure or any non-200 upstream status, which is what
|
||||
triggers the 502.
|
||||
- `server/internal/handler/skill_search_test.go:118-141` —
|
||||
`TestSearchSkillsUpstreamUnavailableReturnsStructuredError` asserts a 502 with
|
||||
`code == "upstream_unavailable"` when upstream returns 502.
|
||||
|
||||
## ClawHub upstream types (for reference)
|
||||
|
||||
- `server/internal/handler/skill.go:616` — `clawHubAPIBase = "https://clawhub.ai/api/v1"`.
|
||||
- `server/internal/handler/skill.go:620-647` — `clawhubSearchResponse`,
|
||||
`clawhubSearchResult` (`slug`, `displayName`, `summary`, `ownerHandle`),
|
||||
`clawhubSkillStats` (`installsAllTime`, `installsCurrent`), `clawhubSkill`.
|
||||
Upstream `stars` is not modeled into any field that reaches the candidate.
|
||||
|
||||
## Post-import content verification
|
||||
|
||||
- `server/cmd/multica/cmd_skill.go:33-38` — `skillGetCmd` (`Use: "get <id>"`).
|
||||
- `server/cmd/multica/cmd_skill.go:119` — `skill get` defaults `--output` to `json`.
|
||||
- `server/cmd/multica/cmd_skill.go:251-279` — `runSkillGet` calls
|
||||
`GET /api/skills/<id>` and prints the skill (body + files) as JSON. This is the
|
||||
first point at which full skill content is inspectable — after import, not
|
||||
during search.
|
||||
|
||||
## Import handoff
|
||||
|
||||
- `server/cmd/multica/cmd_skill.go:60-64` — `skillImportCmd`.
|
||||
- `server/cmd/multica/cmd_skill.go:412-445` — `runSkillImport` POSTs
|
||||
`/api/skills/import` with the selected `--url`. This is the only path that
|
||||
creates a workspace skill. See the importing skill for duplicate handling and
|
||||
agent binding.
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: multica-skill-importing
|
||||
description: Use when a user provides a skill URL, slug, or clear intent to import/install a specific skill into the current Multica workspace. Teaches the workspace import API/CLI path (POST /api/skills/import), the supported URL source families, the SkillWithFilesResponse shape returned on success, duplicate 409 handling with the existing_skill body, additive agent binding vs replace-all, and the reserved SKILL.md supporting-file rule. Do not use it to decide which skill the user needs (load multica-skill-discovery for that), and never treat an external local installer like npx skills add as the final Multica install.
|
||||
description: Use when a user provides a skill URL, slug, or clear intent to import/install a specific skill into the current Multica workspace. Teaches the workspace import API/CLI path (POST /api/skills/import), the supported URL source families, the SkillWithFilesResponse shape returned on success, duplicate 409 handling with the existing_skill body, additive agent binding vs replace-all, and the reserved SKILL.md supporting-file rule. Do not use it to decide which skill the user needs, and never treat an external local installer like npx skills add as the final Multica install.
|
||||
user-invocable: false
|
||||
allowed-tools: Bash(multica *)
|
||||
---
|
||||
@@ -11,7 +11,9 @@ Use this skill when the user already provided a skill URL, slug, or a clear inte
|
||||
to import a specific skill into the current Multica workspace.
|
||||
|
||||
Do not use this skill to decide which skill the user needs. If the user only
|
||||
describes a capability and no URL is known, load `multica-skill-discovery` first.
|
||||
describes a capability and no URL is known, external search may produce candidate
|
||||
URLs, but this import skill starts only once a URL or concrete import target is
|
||||
known.
|
||||
|
||||
Every claim below is traced to source in
|
||||
`references/skill-importing-source-map.md`. When in doubt, read that file.
|
||||
@@ -57,7 +59,8 @@ multica skill import --url github.com/owner/repo/blob/main/path/to/SKILL.md --ou
|
||||
|
||||
## Direct URL flow
|
||||
|
||||
1. If the user gave a URL, do not search first. Import it directly:
|
||||
1. When the request contains a concrete URL, the import endpoint can be called
|
||||
directly; search is not required by the API:
|
||||
|
||||
```bash
|
||||
multica skill import --url <url> --output json
|
||||
@@ -78,8 +81,8 @@ supporting `files` array. Report the relevant fields:
|
||||
Because the response is structured, read these returned fields instead of guessing
|
||||
whether the import succeeded.
|
||||
|
||||
3. If the user wants an agent to use the skill, bind the returned skill id
|
||||
additively. `add` preserves existing assignments and appends the new id:
|
||||
3. Agent-skill binding is a separate mutable operation. `add` preserves existing
|
||||
assignments and appends the new id:
|
||||
|
||||
```bash
|
||||
multica agent skills add <agent-id> --skill-ids <skill-id> --output json
|
||||
@@ -92,15 +95,12 @@ target skill id is present before claiming the skill is available to that agent.
|
||||
## Additive add vs replace-all set
|
||||
|
||||
`multica agent skills add` is additive: the server inserts the assignments without
|
||||
clearing existing ones (`AddAgentSkills`). This is the default for "let this agent
|
||||
also use the new skill."
|
||||
clearing existing ones (`AddAgentSkills`).
|
||||
|
||||
`multica agent skills set` is replace-all: the server clears every current
|
||||
assignment, then re-adds exactly the ids you pass (`SetAgentSkills`).
|
||||
Use `set` only when the user explicitly wants to replace the full skill list.
|
||||
For a normal "add this one," never use `set`, and never pass only the new id to
|
||||
`set` — that
|
||||
would silently drop every other skill the agent already has.
|
||||
`set` is the replacement path. Passing only one id to `set` leaves the agent with
|
||||
only that one skill and drops every previous assignment.
|
||||
|
||||
## Reserved SKILL.md supporting file
|
||||
|
||||
@@ -177,7 +177,8 @@ Correct import:
|
||||
multica skill import --url https://skills.sh/owner/repo/skill --output json
|
||||
```
|
||||
|
||||
Correct follow-up when the skill must be available to an agent:
|
||||
Agent binding after import, when the caller intentionally wants to mutate that
|
||||
agent's skill assignments:
|
||||
|
||||
```bash
|
||||
multica agent skills add <agent-id> --skill-ids <skill-id> --output json
|
||||
|
||||
@@ -44,8 +44,9 @@ Fix login MUL-2759 # links only — keyword not
|
||||
```
|
||||
|
||||
Consequence: a bare title prefix or a branch reference links the PR but does not
|
||||
close the issue on merge. Use a closing keyword only when merge should move the
|
||||
issue to `done`.
|
||||
close the issue on merge. A closing keyword immediately adjacent to the issue key
|
||||
records close intent; on merge, that close intent can move the linked issue to
|
||||
`done`.
|
||||
|
||||
## Reading a linked PR's real state
|
||||
|
||||
@@ -74,13 +75,14 @@ Returns `{"pull_requests": [...]}`. Each element exposes:
|
||||
So "is it merged?" is `state == "merged"` (or `merged_at != null`); "is it still
|
||||
a draft?" is `state == "draft"`; CI status is `checks_conclusion`.
|
||||
|
||||
If the command returns no linked PRs after you opened one, the title/body/branch
|
||||
is missing the issue key — fix the PR rather than asserting the issue is linked.
|
||||
If the command returns no linked PRs after a PR was opened, the link scanner did
|
||||
not observe a routable issue key in the PR title/body/branch.
|
||||
|
||||
## Metadata: high-signal keys only
|
||||
|
||||
Read metadata on entry when the runtime supplies issue context. Write a key only
|
||||
when a future run on the same issue is likely to re-read it.
|
||||
Metadata is durable issue state. Reading metadata is safe. Writing a metadata key
|
||||
is a state mutation and should be tied to an explicit task requirement to record
|
||||
that state for later readers or runs.
|
||||
|
||||
High-signal keys (reuse these names so queries stay consistent):
|
||||
|
||||
@@ -112,7 +114,8 @@ on it. These are the contracts, not advice:
|
||||
- **`backlog`** parks an agent-assigned issue: the assignee is set but no task
|
||||
fires. Moving `backlog → todo` (or any non-done/non-cancelled status) enqueues
|
||||
the assigned agent then.
|
||||
- **`in_review`** is the normal state once a PR is open and awaiting review.
|
||||
- **`in_review`** is an accepted issue status. Some workflows use it while a PR
|
||||
is open and awaiting review; moving to it is an explicit mutation.
|
||||
- **`done`** on a child issue posts a system comment on its parent. If a PR
|
||||
carries close intent (`Closes MUL-XXXX`), it advances the issue to `done`
|
||||
itself on merge — you do not also need to flip it manually.
|
||||
|
||||
@@ -235,7 +235,7 @@ func TestSkillImportingSkillCoversWorkspaceImportContracts(t *testing.T) {
|
||||
"multica agent skills add <agent-id> --skill-ids <skill-id> --output json",
|
||||
"multica agent skills list <agent-id> --output json",
|
||||
"replace-all",
|
||||
"Use `set` only when the user explicitly wants to replace",
|
||||
"`set` is the replacement path",
|
||||
"references/skill-importing-source-map.md",
|
||||
}
|
||||
for _, want := range mustContain {
|
||||
@@ -259,59 +259,6 @@ func TestSkillImportingSkillCoversWorkspaceImportContracts(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkillDiscoverySkillCoversMetadataOnlyPreImportContracts(t *testing.T) {
|
||||
skill, ok := findSkill(t, "multica-skill-discovery")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
fm, body, _ := splitFrontmatter(skill.Content)
|
||||
|
||||
if got := strings.TrimSpace(fm["user-invocable"]); got != "false" {
|
||||
t.Errorf("user-invocable = %q, want false (skill discovery guidance triggers from context)", got)
|
||||
}
|
||||
if got := strings.TrimSpace(fm["allowed-tools"]); !strings.Contains(got, "Bash(multica *)") {
|
||||
t.Errorf("allowed-tools = %q, want access to the Multica CLI", got)
|
||||
}
|
||||
|
||||
// repo/github_stars are intentionally NOT pinned: searchClawHubSkills never
|
||||
// populates them (skill.go), so they are always null and the skill must not
|
||||
// teach them as live selection signals.
|
||||
mustContain := []string{
|
||||
"multica skill search <query> --output json",
|
||||
"GET /api/skills/search?q=...",
|
||||
"clawhub.ai",
|
||||
"upstream_unavailable",
|
||||
"metadata-only",
|
||||
"full content verification happens after import",
|
||||
"install_count",
|
||||
"multica skill import --url <selected-url> --output json",
|
||||
"not `npx skills add`",
|
||||
"discovery is not installation",
|
||||
"references/skill-discovery-source-map.md",
|
||||
}
|
||||
for _, want := range mustContain {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Errorf("skill-discovery skill missing %q", want)
|
||||
}
|
||||
}
|
||||
|
||||
mustNotContain := []string{
|
||||
"content match in `SKILL.md`, not only the title",
|
||||
"If a candidate looks good from the search result but the `SKILL.md` does not",
|
||||
"source reputation, and SKILL.md content",
|
||||
"verify before import",
|
||||
}
|
||||
for _, forbidden := range mustNotContain {
|
||||
if strings.Contains(body, forbidden) {
|
||||
t.Errorf("skill-discovery skill should not imply remote content preview %q", forbidden)
|
||||
}
|
||||
}
|
||||
|
||||
if !skillHasFile(skill, "references/skill-discovery-source-map.md") {
|
||||
t.Errorf("skill-discovery skill missing supporting file references/skill-discovery-source-map.md")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreatingAgentsSkillCoversAgentCreationContracts(t *testing.T) {
|
||||
skill, ok := findSkill(t, "multica-creating-agents")
|
||||
if !ok {
|
||||
@@ -331,7 +278,7 @@ func TestCreatingAgentsSkillCoversAgentCreationContracts(t *testing.T) {
|
||||
"`description` is a catalog summary",
|
||||
"`instructions` is the runtime behavior contract",
|
||||
"multica agent create --name <name> --runtime-id <runtime-id>",
|
||||
"Prefer `--model`",
|
||||
"`model` is a first-class persisted column",
|
||||
"custom_env",
|
||||
"--custom-env-stdin",
|
||||
"--custom-env-file",
|
||||
@@ -495,7 +442,7 @@ func TestProjectsAndResourcesSkillCoversDurableContext(t *testing.T) {
|
||||
"multica project resource list <project-id> --output json",
|
||||
"multica project resource add <project-id> --type github_repo --url <github-url> --output json",
|
||||
"multica project resource add <project-id> --type local_directory",
|
||||
"Do not add a resource just because you need a one-off checkout",
|
||||
"Project resources are durable and affect future tasks",
|
||||
"github_repo.resource_ref.url",
|
||||
"references/projects-and-resources-source-map.md",
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user