Skip to content
Back to skills

Setup Shoulder Daemon

BSecurity

Interview the user and configure shoulder-daemon on their behalf - decision model and its key, memory backend, ranking model, and the mcp-memory-service container when they want one - running every download and container start here, then restarting the daemon and proving it with doctor. Use when the plugin has just been installed, when someone asks to set up, configure, reconfigure, or fix shoulder-daemon, when they want to change its model, provider, pickiness or memory store, or when `shoul...

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 24, 2026
ai-agentsgoshellbashdockerapibackend

Works with

  • terminal
  • api
  • mcp

Security analysis

B80/100
  • mediumUses curl or wget to download content
  • criticalSends environment variables or credentials to an external URL

Pro scans all 2 files and shows the line behind each finding

Scanned October 4, 2026

npx -y skills add QuittyMR/shoulder-daemon --skill setup-shoulder-daemon --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Setup Shoulder Daemon?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Setup Shoulder Daemon
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/quittymr-setup-shoulder-daemon/badge)](https://www.skillsdirectory.com/skills/quittymr-setup-shoulder-daemon)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: setup-shoulder-daemon
description: Interview the user and configure shoulder-daemon on their behalf - decision model and its key, memory backend, ranking model, and the mcp-memory-service container when they want one - running every download and container start here, then restarting the daemon and proving it with doctor. Use when the plugin has just been installed, when someone asks to set up, configure, reconfigure, or fix shoulder-daemon, when they want to change its model, provider, pickiness or memory store, or when `shoulderd doctor` reports NONE, UNREACHABLE, FAILED TO OPEN, MISMATCH, STALE, REJECTED, or no decision model.
allowed-tools: Bash, Read, Write, AskUserQuestion
---

# Setting up shoulder-daemon

Everything about the daemon is environment driven, and it reads one file itself
so that nothing has to be exported by whoever launches it:
`$SHOULDER_ENV_FILE`, or `${XDG_CONFIG_HOME:-~/.config}/shoulder-daemon/env`.
Configuring the daemon means writing that file and restarting. Anything already
in the process environment wins over the file, which is the one way a setting
you wrote can appear not to take.

Do the whole thing here. The user should not have to open a terminal, except
for the single case in "Keys" below where they must, and you tell them exactly
what to type.

## 1. Preflight, before you ask anything

Run these first and let the answers cancel questions. A skill that asks about
something already configured looks like it did not look.

```bash
# $CLAUDE_PLUGIN_ROOT is set for a plugin's own components; the fallback is for
# a checkout, where this file may have been read directly.
S="${CLAUDE_PLUGIN_ROOT:-adapters/claude-code}/skills/setup-shoulder-daemon/scripts/env-set.sh"
[ -x "$S" ] || S="$(find ~/.claude "$PWD" -name env-set.sh -path '*setup-shoulder-daemon*' 2>/dev/null | head -1)"
"$S" path                                    # the file you are about to write
[ -f "$("$S" path)" ] && sed -n 's/^[[:space:]]*\(export[[:space:]]\{1,\}\)\{0,1\}\([A-Za-z_][A-Za-z0-9_]*\)[[:space:]]*=.*/\2/p' "$("$S" path)"   # names only, never values
command -v shoulderd || echo "no shoulderd on PATH"
shoulderd doctor 2>&1 || true
shoulderd config show 2>&1 || true           # provider, model and pickiness of a running daemon
```

Never print the contents of the env file as-is: it holds API keys and this
session's transcript is stored on disk. The `sed` above prints names alone,
even where a quoted value runs over several lines.

Everything `"$S"` does except `path` goes through `shoulderd env`, so it needs
a binary that has it: the checkout's, which `make install-plugins` records in
the env file, or one on PATH or fetched by the plugin. When `"$S"` says there is
none, or that it is too old, relay its message to the user; it names the fix
for either kind of install.

Read the report:

- `relay: ...` not answering, or no `shoulderd` at all - the daemon is fetched
  at the next session start by the plugin's own `scripts/ensure-daemon.sh
  --fetch`. Run that script now rather than downloading anything yourself; it
  verifies the release checksum, links the binary into `~/.local/bin`, and takes
  the lock that stops two editors starting two daemons.
- `plugin: STALE` - the harness is running the copy of the adapter it took at
  install time. Reinstall the plugin, or `make install-plugins` from a checkout.
  Configuration will not fix it and it makes every later check unreliable.
- `auth: N REJECTED` - `SHOULDER_TOKEN` differs between the daemon and the
  harness. Never write a token yourself: the daemon generates one and syncs it
  into the env file. Remove any `SHOULDER_TOKEN` line the user added by hand and
  restart.
- `memory: UNREACHABLE` - a store is configured that cannot be read. That is
  question 2 below, and it is urgent: sessions look normal while nothing is kept.
- `memory: FAILED TO OPEN` - the built-in store could not be opened at start,
  for the reason printed. The file is not the problem; fix the cause (usually
  ownership of the store's directory or volume) and restart.
- `llm: NONE` - no decision model. That is question 1. `llm: none (triage
  only: jev)` is a triage running without one, which is supported; ask whether
  the user wants a decision model as well rather than treating it as broken.
- `relay unreachable` followed by the start command's output - `make up` or
  `SHOULDER_START_CMD` failed; the lines say why. Its full output is in
  `~/.local/state/shoulder-daemon/up.log`.
- A `make up` warning that `deploy/.env` still sets some names - move any
  daemon setting among them into the env file with `"$S" copy NAME` after the
  user exports it, or `"$S" set NAME VALUE` for a non-secret, then have the
  user delete those lines from `deploy/.env`. Never read that file yourself.
- `MISMATCH` under `memory:` or `llm:` - the env file asks for something other
  than what the daemon runs, and the daemon took its value from that file or
  from nothing: it started before the file was edited, or from another file,
  which the line names. Restart it (section 5) before asking anything; the file
  may already say what the user wants.
- `note:` under the same lines - the daemon runs a value from its own process
  environment or from `config set`, chosen over the file. Not a failure; ask
  whether the file should say the same.

## 2. The interview

Use AskUserQuestion, one question at a time, recommended option first. Skip any
question preflight already answered, and say what you found instead of asking.

**Q1 - the decision model.** This is the only setting the daemon cannot work
without; with none it observes every event and stays silent. `SHOULDER_LLM`
names a connector and takes a comma-separated list to fail over through.

| Connector | Endpoint | Default model | Key |
|---|---|---|---|
| `gemini` | Google AI | `gemini-flash-lite-latest` | `GEMINI_API_KEY` |
| `openrouter` | OpenRouter | `google/gemini-2.5-flash-lite` | `OPENROUTER_API_KEY` |
| `glm` | z.ai | `glm-4.7-flash` | `GLM_API_KEY` |
| `glm-coding` | z.ai coding plan | `glm-5.3-flash` | `GLM_API_KEY` |
| `opencode-go` | OpenCode Go | `glm-5.3-flash` | `OPENCODE_API_KEY` |
| `openai` | OpenAI | `gpt-5.2-mini` | `OPENAI_API_KEY` |
| `local` | Ollama on `127.0.0.1:11434` | `qwen2.5-coder:7b` | none |

Recommend by latency, not by quality: the pass runs while the user's prompt is
being answered and advice that lands after the assistant has already chosen what to do is
worth nothing. A flash-tier model beats a better one that thinks for twenty
seconds. Put whichever connectors already have a key first in the list, and
offer `local` to anyone who wants no key at all - check `curl -sf
http://127.0.0.1:11434/api/tags` before offering it, and say the model has to be
pulled with `ollama pull` if it is not there.

**Q2 - where facts are kept.** Three options, and the default is genuinely fine:

- **Built-in store** (`SHOULDER_MEMORY=local`, the default). One JSON file at
  `~/.local/share/shoulder-daemon/facts.json`. Nothing to install, nothing to
  keep running, no container. Recommend this unless the user asks for more.
- **Docs store** (`SHOULDER_MEMORY=docs`). The same facts as markdown bullets
  under `docs/*.shoulder.md` in each checkout, committed and read by the whole
  team. Offer it when the user talks about sharing facts with colleagues.
- **mcp-memory-service** (`SHOULDER_MEMORY_URL=http://127.0.0.1:8100`). Better
  recall and shareable between machines, at the cost of a container that has to
  stay up. Section 3 is the whole procedure. Only offer it from a checkout,
  because starting it needs `deploy/docker-compose.yml`.

Facts do not migrate between stores by themselves. If the user is switching an
established install, offer `shoulderd memory migrate --local` (and `--global`)
before you restart on the new one, and say plainly that skipping it strands what
they have.

**Q3 - how recall ranks, and how much it stores.** Both have workable defaults;
ask only if the user is interested or preflight shows they have already tuned
one.

- `SHOULDER_EMBEDDING=glove` (default) is compiled into the binary and needs no
  download. `minilm` is a 91 MB transformer fetched once into
  `~/.cache/shoulder-daemon/models`; recall is better and the store uses `glove`
  until it has arrived, so nothing is broken while it downloads. If they choose
  it, start the daemon and let it fetch, and tell them the first minutes rank on
  `glove`. Ignore it entirely under mcp-memory-service, which embeds its own.
- `SHOULDER_PICKINESS` is `eager, open, balanced, careful, strict` or `0-4`, and
  is how reluctant the store is to keep a new fact. Lower keeps more and needs
  tidying more often; higher keeps the store clean and misses rules the user
  only implied.

## 3. mcp-memory-service, when they chose it

Only reachable from a checkout. Confirm one, then, in this order:

```bash
make memory                    # first start pulls an embedding model, minutes
until curl -sf http://127.0.0.1:8100/api/health >/dev/null; do sleep 5; done
"$S" set SHOULDER_MEMORY_URL http://127.0.0.1:8100
```

Run the wait in the background rather than blocking, and report progress. Do not
write `SHOULDER_MEMORY_URL` before the service answers: a URL pointing at
nothing is worse than no URL, because the daemon accepts it, reports itself
healthy, and drops every search and every write with only a warning in its log.

Then tell the user two things they will otherwise discover the hard way:

- Under rootless podman, `restart: unless-stopped` needs
  `systemctl --user enable --now podman-restart.service` and lingering
  (`loginctl enable-linger`), or the store is gone after a reboot and the daemon
  goes on reporting it as configured. Check both and offer to enable them.
- `make down` removes the store along with the relay, and `make memory` is what
  brings it back. `make up` starts what is not running and recreates the relay
  alone when its env file changed, leaving a running store untouched; `make
  update` selects it, so an update keeps it.

`SHOULDER_MEMORY_KEY` carries the service's API key if it demands one.
`SHOULDER_MEMORY_URL` wins over `SHOULDER_MEMORY`, so do not set both and expect
the second to matter - the daemon warns about exactly this at startup.

## 4. Keys

Never ask the user to paste an API key into this conversation. The transcript is
written to disk, and shoulder-daemon itself reads transcripts.

For the connector they chose, in order:

1. `"$S" has GEMINI_API_KEY` - already in the env file or this environment.
   Nothing to do; say so.
2. In the environment but not the file: `"$S" copy GEMINI_API_KEY`. That reads
   the value by name and writes it with mode 600 without printing it. Do this
   whenever the key is only exported in a shell, because a daemon started by a
   hook does not reliably inherit it.
3. Neither: stop and tell the user, with the exact line to run themselves,
   pointing out they can run it in this session by prefixing it with `!`:

   ```
   ! "$S" set GEMINI_API_KEY sk-...
   ```

   Give them the URL to get one from, then wait. Do not configure around a
   missing key or write a placeholder: an env file with `GEMINI_API_KEY=` in it
   reports as configured and fails on the first prompt.

## 5. Write, restart, verify

Write with the helper, never with an editor or a shell redirect - it replaces a
setting in place and carries every other line, including the generated
`SHOULDER_TOKEN` and anything the user wrote by hand, across untouched:

```bash
"$S" set SHOULDER_LLM gemini
"$S" set SHOULDER_MEMORY docs
```

The daemon reads the file only at startup, so restart it. Do not hand-roll a
start command; the plugin's script holds the lock, finds the right binary,
prefers a `shoulderd` on `PATH`, and honours `SHOULDER_START_CMD`:

```bash
pkill -x shoulderd || true                       # or: make up, which recreates a container whose file changed
"$CLAUDE_PLUGIN_ROOT"/scripts/ensure-daemon.sh --fetch
until curl -sf --max-time 1 http://127.0.0.1:8787/healthz >/dev/null; do sleep 1; done
shoulderd doctor
```

`shoulderd config set --provider=... --model=... --pickiness=...` changes a
running daemon without a restart, but writes nothing down, so it is for trying a
setting out - not a substitute for the file. While a tried value differs from
the file, doctor prints a `note:` for it and does not fail; write the value with
the helper and restart once the user settles on it.

Then verify, and treat the verification as the deliverable:

- `shoulderd doctor` must show `memory: ok (...)` and `llm: <provider> (<model>)`
  with the provider the user chose, and no `MISMATCH` line. `memory: NONE`,
  `UNREACHABLE` or `FAILED TO OPEN`, `llm: NONE`, or a `MISMATCH` means the work
  is not done. Doctor exits 1 for any of these, and also for hook events it has
  not seen yet, so read the lines rather than the exit code.
- `doctor` also reports hook events it has never seen. Some only appear after
  the user's next prompt, so say which ones are still outstanding rather than
  claiming a clean result you have not seen.
- The plugin's hooks only reload when the editor restarts. If you changed
  anything the adapter carries, say so.

## 6. Report

Say what you set, where the file is, what you did not set and why, and the one
thing that needs the user - a restarted editor, a key, a `pull`. Never claim a
setting works because you wrote it; claim it because `doctor` said so.

Files in this skill

  • SKILL.md10.7 KB
  • scripts/env-set.sh2.7 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…