# Agent Skills

Install SKILL.md skill packages for your tenant, have them vetted before they run, and attach them to managed agents that load each one only when it is relevant.

A **skill** is a folder of instructions an agent can pick up when a task needs it, in the open [Agent Skills](https://agentskills.io/specification) format: a `SKILL.md` file with a short YAML header, and optional `references/`, `assets/` and `scripts/` files beside it.

```markdown
---
name: refund-triage
description: Decide whether a refund request is in policy. Use for refund or chargeback tickets.
license: Apache-2.0
allowed-tools: servicenow/get_order
---
# Refund triage
Check the order, then read references/policy.md before answering.
```

## How an agent uses a skill

Skills load in stages, so a long skill costs nothing until it is needed:

1. The agent's prompt lists each attached skill by **name and description only**.
2. When the model decides a skill is relevant it calls `skill_load`, which returns the instructions in `SKILL.md` and the list of the skill's other files.
3. If those instructions point to a file, the model calls `skill_read` for that file.

Each load is recorded in the audit log as `agent.skill.load`, with the skill, the version's digest and the file. The text itself is not copied into the log. Skill text reaches the model as a tool result, fenced like any other tool result.

A skill gives the agent no new permissions. Its `allowed-tools` list is a declaration: reviewers see it, and attaching checks it against the agent's tools. The agent still calls tools with its own tool list and Cedar policies.

## Install a skill

Send the package's files as text to `POST /api/skills` (needs `governance.write`):

```json
{"files": {"SKILL.md": "---\nname: refund-triage\n...", "references/policy.md": "..."},
 "signature": {"kid": "acme-2026", "signature": "<base64 Ed25519>"},
 "source": "git@github.com:acme/skills.git#v1.2"}
```

To check a package without installing it, send the same body to `POST /api/skills/scan`. Skills are stored per tenant: a skill installed in one tenant is not visible in another.

## What is checked before a skill runs

| Check | What it looks for | Result |
| --- | --- | --- |
| Format | The spec's header fields and limits; text files only; plain relative paths | Refused |
| Hidden text | Invisible characters inside words, bidirectional overrides | Critical: blocked |
| Remote execution | A download or decoded blob piped into a shell | Critical: blocked |
| Prompt injection | "Ignore previous instructions" and the injection phrasings Liyagent already screens tools for | High: held for review |
| Secrets and exfiltration | Credential files (`.env`, SSH keys, cloud credentials), request-capture hosts, URLs filled from variables | High: held for review |
| Memory tampering, encoded blobs, scripts | Asks to rewrite memory or instructions; long base64; executable files (never run by Liyagent) | Shown to the reviewer |
| Signature | Ed25519 over the package digest by a key the tenant trusts | A bad or untrusted signature blocks |

**Settings › `skills_review`** decides who approves:

- `warn` (default): a version with no critical or high finding and no bad signature is approved on install (`skill.approved` by `auto-review`). A version with a high finding waits for an administrator.
- `enforce`: every version waits for an administrator, a version not signed by a trusted key is blocked, and attaching a skill whose `allowed-tools` the agent lacks is refused.

Installing a new version never changes what agents run. The approved version stays in use until the new one is approved.

## Review a waiting version

- `GET /api/skills/{name}` shows the current version and the candidate, with the scan findings, signature status, manifest and files.
- `POST /api/skills/{name}/approve` with `{"digest": "<the candidate's digest>", "reason": "..."}` approves it. If the candidate changed since you read it, the digest no longer matches and the approval is refused. A blocked version cannot be approved.
- `POST /api/skills/{name}/reject` rejects it. `DELETE /api/skills/{name}` removes the skill.

Every install, approval, rejection and removal is in the audit log (`skill.*`), with the scan verdict, findings, signature status and declared tools.

## Trust a publisher's key

`POST /api/skills-trust` with `{"kid": "acme-2026", "public_key": "<base64 raw Ed25519 public key>", "label": "Acme platform team"}`. The publisher signs `liyagent-skill-v1:` followed by the package digest, the SHA-256 shown by the scan. `DELETE /api/skills-trust/{kid}` stops trusting a key.

## Attach skills to an agent

`PUT /api/agents/{agent}/skills` with `{"skills": ["refund-triage"]}` (needs `agents.update` and Cedar `update` on the agent). Only skills with an approved version can be attached. The answer lists `gaps`: tools a skill declares that the agent does not have. Under `warn` the gaps are reported and audited. Under `enforce` the attach is refused. `GET /api/agents/{agent}/skills` lists what is attached and active.

Agents with no skills attached are unchanged: no skill tools, no skill text in the prompt. `skills_enabled` turns the feature off everywhere.
