Connect an agent to Smeldr
An agent that edits code should be able to ask which Decisions apply to the files it touches. It works with Smeldr over MCP, with a token of its own. This page gives an agent what it needs: a token classed as an agent, a grant that says what it may read, and the server's address. Then it shows what the agent can do with that, and what stays with people.
It assumes a server set up as in Getting started, with the places from Set up the example places: the Domain DOM2 "Payments", and its Area ARE1 "Billing", which covers internal/billing/** in acme/app. Your shell has your own token loaded and the mcp function from Getting started defined.
Where the two shells differ, a step shows the bash form first, then the PowerShell form. Commands shown once run unchanged in bash and in Windows PowerShell, with the files named as in Getting started. cmd.exe is not covered. The steps were run as written on Linux and in Windows PowerShell on Windows 11. Smeldr's automated tests also run the bash steps as written on macOS, on Apple silicon and on Intel, on the macOS versions the tests use.
Make the agent's token
./smeldr-cli token create my-agent author 30 --class agent --reason "my agent" > agent.json
AGENT_TOKEN=$(grep -o '"token": "[^"]*"' agent.json | cut -d'"' -f4)
AGENT_ID=$(grep -o '"token_id": "[^"]*"' agent.json | cut -d'"' -f4)
In PowerShell:
./smeldr-cli token create my-agent author 30 --class agent --reason "my agent" | Set-Content agent.json
$agent = Get-Content agent.json -Raw | ConvertFrom-Json
$AGENT_TOKEN = $agent.token
$AGENT_ID = $agent.token_id
--class agent is what keeps the right to ratify from it, whatever it is granted later. author is the token's own role, and it decides which tools the agent gets: with author it can read and propose, but it has no tools to move an item to another state or read an item's history; editor adds those. The token is shown once: keep it where the agent's configuration reads it, and delete agent.json when you have.
Give it a grant
The token's role gives the agent its tools; a grant decides where it may use them. A new token reads nothing in a place: every call it makes on a placed Decision is refused with forbidden, of the refusal type capability.denied, until it holds a grant. Give it the least it needs:
| Role | What the agent may do |
|---|---|
area-reader over one Area | read the Area and the Decisions placed in it |
domain-reader over one Domain | read the Domain, its Areas and the Decisions in them |
author | read every place, and propose and place Decisions |
editor | what author may, and delete and manage items |
An agent that only reads before it changes code needs a reader role over the place it works in. A reader role is anchored on that place, named by its address:
./smeldr-cli grant "$AGENT_ID" area-reader --anchor ARE1 --rev 0 --reason "reads billing"
An anchor that names no place is refused.
An agent that also proposes Decisions needs author. Grant it as well; the agent's identity now holds one grant, so name its version with --rev 1:
./smeldr-cli grant "$AGENT_ID" author --rev 1 --reason "reads and proposes decisions"
Point it at the server
The server serves MCP over HTTP at <BASE_URL>/mcp, here http://localhost:8080/mcp. An MCP client that speaks HTTP needs that address and one header, Authorization: Bearer followed by the agent's token. Nothing else needs installing on the Smeldr side. If you run agents through a harness, the harness holds the token and makes these calls. MCP overview lists what the tools are for.
What the agent can do
Ask which Decisions apply to the files it is about to change. That is the tool decisions_for_paths. smeldr-cli makes the same call, so you can see the answer with the agent's token:
SMELDR_TOKEN=$AGENT_TOKEN ./smeldr-cli decisions-for-paths --repo acme/app internal/billing/invoice.go docs/readme.md
In PowerShell:
$own = $env:SMELDR_TOKEN
$env:SMELDR_TOKEN = $AGENT_TOKEN
./smeldr-cli decisions-for-paths --repo acme/app internal/billing/invoice.go docs/readme.md
$env:SMELDR_TOKEN = $own
AREA NAME APPROVAL PATHS
ARE1 Billing requires-approval 1
DECISION PLACE TITLE
D2 organization ORG1 Store timestamps in UTC, with the offset kept
D3 domain DOM2 Payments use idempotency keys
D4 area ARE1 Invoices are immutable
3 Decisions in force; 1 paths uncovered
The answer also says that ARE1 requires approval for a change, and that docs/readme.md is in no Area. With only the area-reader grant, the agent would see D4 alone: it reads what its grants let it read, and nothing else is listed. Find the Decisions that apply explains the answer in full.
Propose a Decision. With author, the agent proposes, and places the proposal in the Area it is about:
agent() {
curl -s http://localhost:8080/mcp/message \
-H "Authorization: Bearer $AGENT_TOKEN" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
--data @"$1"
}
cat > agent-propose.json <<'EOF'
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"create_decision","arguments":{
"title":"Invoice numbers are never reused",
"scope":"engineering",
"body":"A cancelled invoice keeps its number."}}}
EOF
NEW=$(agent agent-propose.json | grep -o 'address\\":\\"D[0-9]*' | grep -o 'D[0-9]*')
cat > agent-place.json <<EOF
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"assert_relation","arguments":{
"source_type":"Decision","source_id":"$NEW",
"relation_kind":"belongs_to_area",
"target_type":"Area","target_id":"ARE1"}}}
EOF
agent agent-place.json | grep -o '"isError":[a-z]*'
./smeldr-cli search --type Decision --place ARE1
In PowerShell:
function agent($file) {
# The answer is saved and read back as UTF-8, so the console shows it as the server sent it.
curl.exe -s http://localhost:8080/mcp/message `
-H "Authorization: Bearer $AGENT_TOKEN" `
-H 'Content-Type: application/json' `
-H 'Accept: application/json, text/event-stream' `
--data "@$file" -o answer.json
Get-Content answer.json -Encoding UTF8
}
@'
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"create_decision","arguments":{
"title":"Invoice numbers are never reused",
"scope":"engineering",
"body":"A cancelled invoice keeps its number."}}}
'@ | Set-Content agent-propose.json
$answer = agent agent-propose.json | Where-Object { $_ -like 'data:*' }
$NEW = (($answer -replace '^data: ', '' | ConvertFrom-Json).result.content[0].text | ConvertFrom-Json).address
@"
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"assert_relation","arguments":{
"source_type":"Decision","source_id":"$NEW",
"relation_kind":"belongs_to_area",
"target_type":"Area","target_id":"ARE1"}}}
"@ | Set-Content agent-place.json
(agent agent-place.json | Select-String '"isError":[a-z]*').Matches.Value
./smeldr-cli search --type Decision --place ARE1
"isError":false
RECORD TYPE STATE TITLE
D5 Decision proposed Invoice numbers are never reused
D4 Decision ratified Invoices are immutable
2 of 2
The new Decision is proposed in ARE1, and it does not hold until a person ratifies it.
What stays with people
- Ratifying. A token classed as an agent is refused when it tries to ratify a Decision, and a role that ratifies can't be granted or delegated to it. See an agent refused at ratify shows it on your own server.
- Tokens and grants. With a token of the
authororeditorrole, the agent has no tools for making tokens or granting roles. It hasdelegate_item, which hands on a role over one item for a limited time, and only rights the agent holds itself. - Approving a change, except where a person allows it. A pull request's Check is opened by the host's adapter or an operator. An approver answers it, over MCP or the command line. An agent can answer only within one Area, and only when a person who approves changes in that Area grants it
area-approveranchored on that Area. A grant of that role to an agent without an Area, or from someone who does not approve there, is refused.
Smeldr does not run the agent, keep its memory, or read its code against your Decisions. It tells the agent what holds where, records what the agent proposes, and keeps the decisions with people. The merge is held where it happens: GitHub, through a required status check that Smeldr sets and the repository owner requires.