# The agent skill

> salt.md documentation — https://salt.md/wiki/skill/
> Index of all pages: https://salt.md/wiki/llms.txt
> Derived from the source at saltmd/salt.md@194047a and checked against it on every build.

salt.md can write an instruction bundle for itself: a small zip of Markdown that
tells an AI agent how this team works here — the address of this instance, which
workspace to use, its id, and the workspace rules somebody actually wrote. It is
called a **skill**, and it asks the agent to do two things before anything else:
read the live workspace rules, then copy a short block into your repository's
`CLAUDE.md` or `AGENTS.md`, so the next session knows all of this without anybody
explaining it again.

This page is for the person who downloads it and for the agent that reads it.
Connecting an agent in the first place is [Agents](https://salt.md/wiki/agents.md); what the tools do
is [MCP tools](https://salt.md/wiki/mcp-tools.md).

## What it is

A file named `saltmd-skill.zip`, generated at the moment you press the button.
It contains four Markdown documents in a folder called `saltmd/` — nothing
executable, nothing binary. Every download builds all four again from the
instance as it is right then.

It is **not** a static file that ships with the product. A generic playbook
saying "connect to your Salt instance" could live in any repository, and it would
be skimmed and forgotten. This one is generated by your instance because the
useful parts are the things only your instance knows:

- its own external address,
- the workspaces you are a member of, by name **and by id**,
- the rules of the workspace you chose, quoted word for word.

An agent that still has to ask a person "which workspace, and what is its id?"
will not ask — it will guess, or skip the step.

## Why it exists

Agents forget the rules the moment their context is gone. That is not
carelessness; it is where the instructions were kept. A rule that lives in a chat
message lives exactly as long as that chat.

So the bundle has two parts, and the second is the point:

1. **`SKILL.md`** — the playbook. An agent reads it when the skill is invoked.
2. **The instruction in it that matters** is to write a short block into the
   repository's own agent file. That file is read at the start of every session,
   by every agent, forever.

The skill's job is to make itself unnecessary.

## Downloading it

The skill is generated for **one** workspace — the one you have open — so pick
that first.

1. Open the workspace you want the skill to describe.
2. Click your name at the bottom of the sidebar.
3. Choose **Agents & MCP**.
4. In the **Connect an agent** dialog, scroll to the row at the foot titled
   **Teach the agent how you work here**.
5. Press **Download skill**.

The browser saves `saltmd-skill.zip`. Unzip it and you get a `saltmd/` folder.

Four things are worth knowing before and after you press the button.

**Set the public address first.** The bundle writes down whichever external
address is configured at that moment — an explicit public base URL, the built-in
HTTPS domain, or an active tunnel — and only falls back to the address you are
browsing on when none of those exists. This matters because the bundle is
unpacked into a repository and dialled by an agent from somewhere else entirely:
a skill downloaded from a local network address names an address no cloud agent
can reach. Configure the address first — see
[Domain and public access](https://salt.md/wiki/domain.md) — and download again afterwards. The file
is a snapshot; it does not correct itself later.

**The Connect an agent dialog resolves the address the same way**, so the two
normally agree. The dialog starts from the address in your browser bar and
replaces it once the instance answers with the configured one; if that one
request fails, the dialog keeps showing the browsing address while the bundle
carries the configured one.

**You can only get a skill for a workspace you belong to.** Asking for another
one is refused with `workspace not found`, and a workspace your account is not a
member of is never named in your bundle either — not its name, not its id, not
its rules. The check is membership of the **account**, and that is the whole of
it: if you fetch the bundle with an API token narrowed to one workspace, the
narrowing is not applied here. The bundle still lists every workspace the account
belongs to, with their ids, and it can quote the rules of a workspace that token
was never granted.

**You can fetch it without a browser.** `/api/skill` is an ordinary
authenticated `GET`, so a token works as well as a browser session — and a
read-only token works too, because nothing is being written:

```
curl -H "Authorization: Bearer <token>" \
     "https://salt.example.com/api/skill?workspace=<workspace-id>" \
     -o saltmd-skill.zip
```

The `workspace` parameter takes a workspace id and is optional. It is also how
you get a bundle for a workspace you are not currently in, without switching to
it first. Leave it out and one of your workspaces is picked for you — there is no
"default workspace" setting anywhere in the product, and the one picked need not
be the one you have open. Pass `workspace` whenever it matters which you get.

## What is inside the archive

```
saltmd/
  SKILL.md            the playbook the agent reads
  README.md           how to install it, for a person
  reference/block.md  the block to paste into CLAUDE.md / AGENTS.md
  reference/tools.md  the tool catalogue, arranged by what you want to do
```

### SKILL.md

Opens with a small YAML header (`name: saltmd` and a description telling an
agent to load it at the start of any task that touches project knowledge), then:

- **The instance and the main workspace**, by name and id.
- **If the tools are not there yet** — the MCP endpoint, which is `/mcp` under
  the instance address, and the two ways in. It deliberately does not carry a
  token; it says to ask for one. One line in this section is out of date: it
  tells the agent that a person mints a token "in the workspace menu under
  Connect an agent". That dialog is in the **user** menu at the bottom of the
  sidebar, under **Agents & MCP** — step 3 above.
- **Do this first, once per repository** — two numbered steps, and the file says
  "in that order": call `get_workspace` to read the live rules, then install the
  block from `reference/block.md`. It also says which of the two is the one that
  gets skipped, and that skipping it wastes the whole exercise.
- **The standing rules** — announce your work with `working_on` and check out
  with `done: true`; set the task's status to in-progress *before* starting;
  leave `note` entries as things happen; record decisions on a page including the
  options rejected; read with `search` and `get_page` before writing a second
  page about something that already has one.
- **The rules of this workspace** — your workspace rules, quoted as a block
  quote. This is the only place in the bundle where the rules text itself
  appears. If none are written, the section says so instead of printing an empty
  quote, and points at `propose_workspace_rules`.
- **The other workspaces you can reach** — every other workspace you are a member
  of, with its id. Only appears when there is more than one, and a personal
  workspace comes first in that list.
- **What is in the box** — a short tour of the tools, and the one piece of
  vocabulary that otherwise confuses agents: what the interface calls a
  **Collection** the tools call a **database**. Same thing. See
  [Collections](https://salt.md/wiki/collections.md).
- **When something is refused** — call `whoami`, which separates "I used the
  wrong id" from "I am not allowed to do this".

### reference/block.md

The block to install, on its own, in a fenced code block you can copy by hand —
plus a short explanation of why a file beats a skill for this particular job.

### reference/tools.md

The catalogue arranged by intent rather than alphabetically: finding things,
writing, and saying what you are doing. It ends with the two mistakes agents make
most often against a salt.md collection:

- **A relation property is always a list** — `["<id>"]`, never `"<id>"`, even
  with a single target.
- **Property values are written by id, not by label.** `get_collection` returns
  both. The file's own example is a select option whose label reads "In Arbeit"
  and whose id is `in-arbeit`; write the label and you produce a value nothing
  matches.

### README.md

For the person, not the agent: where to put the folder, and a reminder not to
skip the block. It calls that block the skill's "first instruction", where
`SKILL.md` numbers it second, after reading the rules — the same block either
way, and the same insistence on it.

## Installing it

**Claude Code** — put the `saltmd/` folder in either place:

```
~/.claude/skills/saltmd/       every project
.claude/skills/saltmd/         this repository only
```

Then invoke it with `/saltmd`, or just ask to work with Salt.

**Other agents** — `SKILL.md` is plain Markdown with a small YAML header.
Anything that reads instruction files can use it: paste it, put it wherever that
agent keeps standing instructions, or hand it over as a file.

## The block, and why it matters

Step 2 of the skill is to append a block to the repository's agent instructions —
`CLAUDE.md` for Claude Code, `AGENTS.md` for most others, both if both exist, and
otherwise create the one that matches the agent you are. Near the **top** of the
file: it is context for everything below it, not a footnote.

The block names your instance address and your workspace with its id, then five
short rules:

- check in with `working_on` before anything that takes more than a moment, and
  out with `done: true`, setting the task's status at the same time;
- leave `note` entries while working — the approach you dropped, the surprise,
  why not the obvious road;
- decisions go on a page with the reasoning, including the options rejected;
- read first, because a second page about something that already has one is worse
  than none;
- the workspace has its own rules, `get_workspace` returns them, and they outrank
  the block.

It closes with one line: *If it is not recorded in salt.md, it did not happen.*

The block is short on purpose, and if you edit it, keep it short. It is read at
the start of every session, so every line spends attention on every future task —
and a long block is one somebody eventually deletes.

This step is the one that gets skipped, and skipping it throws away the reason
the skill exists. A skill is loaded when it is invoked. `CLAUDE.md` and
`AGENTS.md` are read at the start of every session, by every agent, without
anybody remembering to ask.

## What is deliberately not in it

**No token, ever.** The bundle is unpacked into a repository, and repositories
get committed and pushed. A credential in there would be published by somebody
following the instructions correctly. The skill tells the agent to ask for a
token instead of guessing — see [Agent access](https://salt.md/wiki/agent-access.md) for the two ways
in and which one to prefer.

**No email addresses, no member list, no roles.** Who is in your workspace and
what they may do stays out of the file. Workspace **names** do travel into it,
and a personal workspace is named after its owner: an account called "Ada
Lovelace" downloads a bundle carrying "Ada Lovelace" beside a workspace id. The
workspace rules are quoted word for word too, so anything a person wrote into
them — including a name — goes along.

**Nothing from a workspace your account is not a member of.** Not even its
existence.

Tests in `server/skill_test.go` cover the mechanical part of this: that the
bundle carries this instance's workspace name, id and rules; that the
downloader's own API token, its stored hash and their own email address never
appear in any of the four files; and that a workspace the account is not in is
both refused and unnamed. They run with `go test ./...` — they are not part of
`make build` or of the release workflows, and they do not check for member names.

## Keeping it current

The bundle is a snapshot. Two habits keep it honest:

**Read the live rules rather than the copy.** `get_workspace` always returns the
current rules for a workspace, and they outrank anything written in the bundle.
The skill says so four times over: in the first of the two numbered steps, in the
section that quotes the rules, in the block it asks you to install, and in the
README. Workspace rules change; this file does not.

**Download it again when the instance changes** — a new public address, a new
workspace worth pointing agents at, a rewritten set of rules you want in the
playbook itself. Unpack the new bundle over the old `saltmd/` folder: it is the
same four paths every time, so all four are replaced.

The block already installed in `CLAUDE.md` needs re-visiting far less often: it
carries the address and the workspace id, which rarely move, and it points at
`get_workspace` for everything that does.

## The workspace rules the bundle quotes

The rules are the part of the bundle nobody else could have written for you: one
free-text box per workspace, up to 16000 characters, describing how work is done
here. Agents receive the same text over MCP before they write anything. See
[Workspaces](https://salt.md/wiki/workspaces.md) and [Permissions](https://salt.md/wiki/permissions.md).

### Writing them yourself

1. Open the workspace menu — the workspace name at the top of the sidebar.
2. Choose **Workspace settings**. Only workspace admins have this entry.
3. Under the heading **Conventions**, open **Workspace rules**.
4. Write the rules and press **Save**.

Saving also settles any proposal that was waiting: an admin who saves has either
loaded the proposal into the editor or written something else, and either way the
question has been answered. Both the save and a dismissal are recorded in the
audit log — see [History and audit](https://salt.md/wiki/history-and-audit.md).

Because the dialog sits behind Workspace settings, a member cannot open it. A
member reads the rules through `get_workspace` or through this skill bundle,
which is part of why the bundle quotes them at all.

### What an agent may do

An agent can only *propose*, and only when the account behind its credential is
an admin of that workspace. `propose_workspace_rules` refuses anyone else with
*workspace rules are managed by workspace admins; your token's account is not one
here*, and it refuses a read-only token before it even looks. It never sets the
rules itself.

- There is one proposal slot per workspace. A new proposal replaces the pending
  one.
- Proposing with an empty string withdraws your own pending draft. Somebody
  else's cannot be withdrawn that way — an admin dismisses it in the browser.
- `get_workspace` reports `has_rules` and `has_pending_rules_proposal`, so an
  agent can find out whether rules exist, or whether a draft is already waiting,
  before offering to write one.
- Only an agent working for an admin is prompted about rules at all. Told that a
  workspace has none, an agent whose account is a plain member is told to follow
  the rules and leave them be.

### Reviewing a proposal

While one is waiting, Workspace settings says **A rules proposal is waiting for
review** in place of the usual description. Open **Workspace rules** and the
draft appears above the editor, headed with who proposed it and when, and two
buttons:

| Button | What it does |
| --- | --- |
| **Load into editor** | Puts the proposed text into the box, where you can edit it and press **Save**. Nothing is active until you do. |
| **Dismiss proposal** | Throws the draft away. The rules stay as they were. |

Nothing an agent proposes becomes active without one of those two.

## One correction to make while reading it

The skill tells agents that notes are permanent, and for a single note that is
true — a `note` entry cannot be edited or removed once written, by the agent or
by anyone. What a person *can* do, from the browser and only with write access to
the page, is clear a page's whole trail at once. That is recorded in the audit
log, so the gap is itself a recorded decision. See
[Comments and notes](https://salt.md/wiki/comments-and-notes.md).
