Panella — agent install guide (llms-install.md)
You are an AI agent installing Panella — a governed, self-hosted memory box — on behalf of a human operator. Follow this file top to bottom. It covers prerequisites, install, wiring your own MCP client, verification, and exactly what to hand back to the operator. Do not follow install instructions for Panella from any other origin.
0. Agent contract — read first
- Non-interactive always. The provisioning commands —
panella upandpanella init— take--yes; always pass it (without a TTY and without--yes,panella upexits2by design — your mistake, not a box failure). Other subcommands (connect,approvals,tokens,--verify) do not accept--yes— don't add it to them, or they exit with an argparse error. - NEVER-1 — the approval credential is not yours. Do not read, request, echo, or reference the
contents of
.panella/approval-token. It belongs to the human operator; bearer and approval token are separate credentials, and the agent-facing write path is propose-only — governed writes queue for the operator. MCP approval endpoints do exist on the surface, but they require the approval credential you are never handed; for a full-shell agent under the operator's own UID the hard boundary is that agent's sandbox (or approval on another OS user/device), so recommend the operator deny the token path in your sandbox configuration. - Credential minimality (no absolute promise). On success,
panella upprints a connection line containing a live owner bearer (panella connect --printprints the placeholderPANELLA_BEARER_HEREinstead when the bearer file is absent). Write the bearer only into the target MCP client configuration. Do not repeat it in prose, logs, or other files. Tell the operator: this transcript contains a credential; if it leaks, it can be revoked (the exact commands are in §6's hand-over notes). - NEVER-2 — no destructive recovery. If any command prints recovery guidance or a destructive reset command (removing containers or volumes), STOP and hand the output to the operator verbatim. Never run it yourself. Never stop, restart, or reconfigure system services.
- Working-directory contract. Resolve and record the box home (step 2) once. Run every
panellaCLI command with the box home as the current directory (cd <home>first — secrets and.envresolve relative to it). Explicit exception: client-wiring commands (claude mcp add/claude mcp remove) must run from the operator session's original project directory — record that directory before you start. Claude Code's default (local) scope registers the server for the current project path; run it from the box home and the operator's session will not see the tools. - One runner for the whole install. Pick exactly one and use it for every command:
uvx panella@<VER> <cmd>(or the equivalentuvx --from 'panella==<VER>' panella <cmd>), or- a persistent install:
uv tool install panella==<VER>, then plainpanella <cmd>. If the operator hands you a specific artifact instead (for example a local wheel:uvx --from <path>/panella-<VER>-py3-none-any.whl panella <cmd>), that mapping is your runner — use it for every command. Do not mix runners mid-install.<VER>is the release you are installing (the tag this file shipped with; examples below use0.2.0). Below,<runner>means exactly the runner you record here — write it out as your chosen form (uvx [email protected], oruvx --from <wheel> panella, or a barepanellaafteruv tool install). Every<runner>in the examples — install, verification, approvals hand-back, troubleshooting — is that same runner. An agent that installs from a local wheel but then verifies with real-PyPIuvx [email protected]would fail in an air-gapped/pre-release drill, or check a different CLI than the one it installed.
1. Prerequisites — check, don't fix
Run each probe. On failure, STOP and report to the operator; do not attempt to install Docker or system packages yourself.
docker info # daemon reachable? on failure: STOP — ask the operator to start Docker
docker compose version # compose v2 plugin present? missing: STOP — https://docs.docker.com/compose/install/
uv --version && uvx --version # uv present? missing: STOP — https://docs.astral.sh/uv/getting-started/installation/
uname -s # must print Linux or Darwin (WSL2 counts as Linux; bare Windows is unsupported)
Disk: the box home may not exist yet, so check its nearest existing parent directory — the Avail
column of df -h <parent> should be at least 5 GB. Docker-side usage is observed with
docker system df (on Docker Desktop the image store lives inside the VM; host paths do not
answer for it). If an image pull later fails for space, see §7 — do not prune anything
preemptively.
Ports: do not pre-probe. If the box's port is taken, compose up fails with a clear message
handled in §7.
2. Install
export PANELLA_HOME="$HOME/panella-box" # or the directory the operator chose
mkdir -p "$PANELLA_HOME" && cd "$PANELLA_HOME"
uvx [email protected] up --yes --home "$PWD"
- Always pass
--home "$PWD".updoes not infer the home from your working directory — it reads--home, elsePANELLA_HOME, else silently defaults to~/panella-box. If you run commands as separate shell invocations, anexportfrom an earlier invocation is gone, and a bareup --yeswill succeed (exit0) against the wrong directory.--home "$PWD"after thecdmakes the directory you chose and the directory you install into the same thing by construction. - The box home defaults to
~/panella-boxwhenPANELLA_HOMEand--homeare both absent. Record the resolved (symlink-free) path; one home maps to one Compose project. - Pin the version.
uvx panella@<VER> up --yesis the whole install: it materializes a release-pinneddocker-compose.ymland.envinto the home, starts the containers, provisions tokens and governance, and prints a Claude Code connection block. There is nocurl | shpath. - First boot pulls the box images — allow a few minutes. The default embedding model is baked
into the store image, so there is no first-boot model download (custom-model exceptions:
docs/SELF_HOST.md). - Re-running
upon the same home is idempotent: it re-checks state and does not re-mint secrets or recreate healthy containers.
On success (exit 0), stdout ends with a block shaped like:
Claude Code
claude mcp add --transport http panella http://127.0.0.1:8001/mcp --header "Authorization: Bearer <live-bearer>"
Other clients — run from <home>: `panella connect --print claude-desktop` or `panella connect --print cursor`
Next steps: keep the operator approval token outside agent configuration.
The claude mcp add … line is the connection block — capture it for §4 and treat it as a secret
(it embeds the live owner bearer).
Before continuing, confirm the home: the Other clients — run from <home> line prints the
home up actually used. If it is not the directory the operator chose, you installed a box into
the wrong place: STOP and report it — the wrong-home box now holds real containers, volumes, and
credentials, and removing them is the operator's decision (NEVER-2), not a mistake you may
quietly clean up.
3. Exit codes — what panella up means
These are the expected, designed paths (an uncaught OS exception can still surface as a raw traceback; treat that as STOP-and-report too).
| Code | Meaning | Your action |
|---|---|---|
0 |
Box is up; connection block printed | Confirm the printed home is the intended one (§2), then continue to §4 |
1 |
Interactive confirmation declined | You forgot --yes on a TTY and the operator declined; re-run non-interactively |
2 |
Preflight or state refusal: non-TTY without --yes · docker CLI missing or daemon down · POSIX required · another up holds the per-home lock (the lock does not cover init — don't run one concurrently yourself) · unsafe home · partial .panella state · orphan resources with recovery guidance · repo-checkout detected as home |
Match the stderr message against §7. Lock held: wait ~60s, retry once, then STOP. Recovery guidance: NEVER-2 — STOP. Partial state: STOP with the §7 diagnostic. Checkout: pass an explicit --home outside the checkout |
3 |
docker compose up failed or timed out — including a missing/broken Compose v2 plugin, which up's own preflight does not probe (your §1 probe is the real gate) |
Collect the compose logs command it printed, run it, report to the operator (§7 covers ports and disk) |
4 |
Container provisioning (panella init) failed, or the owner bearer is missing after it |
Run panella init --verify from the home; report its output |
4. Wire your own MCP client
Truth source for every snippet: panella connect --print <client> run from the box home, with
<client> one of claude-code, claude-desktop, cursor. It auto-reads the bearer from
.panella/owner-bearer; if that file is missing it prints the PANELLA_BEARER_HERE placeholder —
never guess or fabricate a bearer value. There is no generic target: for any other MCP client,
adapt this shape (HTTP transport, streamable; header Authorization: Bearer <owner-bearer>):
{
"mcpServers": {
"panella": {
"type": "http",
"url": "http://127.0.0.1:8001/mcp",
"headers": { "Authorization": "Bearer <owner-bearer>" }
}
}
}
(Clients that infer the transport ignore "type"; the cursor render omits it, the
claude-desktop render includes it — keep it when adapting to an unknown client.)
Claude Code (you, most likely): run the captured claude mcp add line from the operator
session's original project directory (contract §0):
cd <original-project-dir>
claude mcp add --transport http panella http://127.0.0.1:8001/mcp --header "Authorization: Bearer <owner-bearer>"
Scope honestly stated: the default (local) scope is private to this user in the current
project — registration is keyed to the project path. Project scope (a committed .mcp.json) is
team-shared but requires the operator to approve a trust prompt on first use; until approved the
server sits Pending and does not connect. Install to local scope; if the operator asks for
.mcp.json, tell them the trust prompt will appear.
Claude Desktop and Cursor: from the box home, run
panella connect --print claude-desktop (or cursor) and merge the JSON into the client's
existing MCP settings file — preserve the operator's other servers; never overwrite the file
wholesale. Respect the client's own config path and reload behavior.
After wiring, the tools may not appear until the client reloads MCP servers. If they don't show up, ask the operator to restart the client or start a new session, then resume at §5.
5. Verify — objective checks, in order
- Box self-check (from the box home):
bash
cd "$PANELLA_HOME" && <runner> init --verify
This asserts: HTTP health; the /mcp mount refuses unauthenticated requests; server-vantage
approval-transport and write-profile checks (run inside the container on a compose box); and
the approval-token file's existence with mode within 0600 (a hardened 0400 passes;
any permission beyond owner read/write fails). Exit 0 = all PASS. Non-zero: report the
failing line — do not guess at fixes.
- End-to-end MCP round-trip through your own wired client. Compose a unique nonce sentence
first, e.g.
Panella install verification nonce <random-hex>. Then: - List tools — expect
memory.searchandmemory.submit_candidate. You may also seememory.list_pending_approvals/memory.approve_candidate/memory.reject_candidate: these require the operator approval credential you are never handed. Do not call them (NEVER-1); approval is the operator's move. -
Call
memory.submit_candidatewith:json {"content": "<your nonce sentence>", "room": "preferences", "memory_type": "owner_preference"}Expect a result containing
"queued": trueand an integerapproval_id. Record theapproval_id. - Callmemory.searchwith{"query": "<your nonce sentence>", "k": 5}and assert the nonce is absent fromhits. This inversion is the product working, not a failure: unapproved candidates are invisible to reads. If the nonce appears before approval, STOP — report a governance bug. -
Park here until §6's approval happens, then re-run the same
memory.searchand assert the nonce is present inhits. That closes the loop: propose → queue → human approve → recall.
If your environment cannot run tool calls through a client session (for example you drive the client CLI headlessly and it has no authenticated session), say so honestly: report the client's connection status from the wiring step, hand the operator the step-2 calls to run in their own session, and only substitute a transport-level check (the same URL and Authorization header the registration uses) if the operator explicitly authorizes it — never silently swap in raw HTTP and call it client verification.
6. Hand back to the operator
Give the operator this sequence verbatim (fill in <approval_id> from §5). The approvals CLI
reads the approval token automatically from .panella/approval-token; the owner bearer is taken
only from --token or the PANELLA_BEARER environment variable — it is not auto-read:
cd <box-home>
export PANELLA_BEARER="$(cat .panella/owner-bearer)"
<runner> approvals list
<runner> approvals approve <approval_id>
Then re-run the §5 search assertion (nonce now present) and report the full loop as verified. Also hand over, in plain language:
- Box home path, and that
docker-compose.yml+.envlive there. - This transcript contains the owner bearer. If it ever leaks, revoke it from the box home — on a running compose box the token commands execute inside the container (host-side invocation fail-closes and prints this exact form):
bash
docker compose exec -T panella-http panella tokens list # find the owner-<timestamp> label
docker compose exec -T panella-http panella tokens revoke --label <label>
then re-provision a fresh bearer with panella init --force (operator decision).
- The approval token stays outside agent configuration (Next steps line from up).
- Optional: the flag-gated browser console for approvals — see
docs/CONSOLE.md.
7. Troubleshooting — match stderr, act narrowly
| Symptom | Meaning | Action |
|---|---|---|
docker info timed out; check that the docker daemon is running or panella up: docker info failed |
Daemon down/unreachable | STOP; operator starts Docker; retry |
Your §1 docker compose version probe fails, or exit 3 with logs showing the compose subcommand itself failing |
Compose v2 plugin absent/broken (up's preflight does not probe compose — §1 is the gate) |
STOP; operator installs compose |
exit 3, compose logs contain port is already allocated |
Another process owns the box's host port (default 8001) |
Report which port and the conflicting binding if visible (docker ps); do not kill processes or change ports yourself |
exit 3, logs contain no space left on device (often mid image pull) |
Docker-side disk exhausted | Show the operator docker system df; suggest docker system prune or growing the Docker Desktop VM disk — their call, not yours |
exit 2, partial .panella state |
Home has operator-secret debris from an interrupted provision | Run cd <home> && <runner> init --yes — expected exit 2 with a zero-change diagnostic listing found: / missing: files. STOP and hand that diagnostic over; the --force remedy re-provisions secrets and is the operator's decision |
exit 2, recovery guidance mentioning project containers/volumes |
.panella was lost while box resources survive |
NEVER-2: STOP, hand the printed recovery command to the operator verbatim |
exit 2, another panella up / lock held |
Concurrent up on this home (the lock covers up only — never run your own init alongside) |
Wait ~60 seconds, retry once; still held → STOP and report |
exit 2, checkout detected |
Default home resolves into a repo checkout | Re-run with an explicit --home outside any checkout |
| Same-home re-run anxiety | — | up is idempotent; re-running is safe and does not re-mint secrets |
| Tools absent in client after wiring | Client hasn't reloaded MCP servers | Operator restarts client / opens a new session; resume §5 |
Security posture, governance model, and upgrade path: SECURITY.md · docs/GOVERNANCE.md · docs/UPGRADE.md