| name | minds-dev-workflow |
| description | End-to-end dev workflow for the minds app stack -- first-time bring-up, every-startup system/vendor/mngr sync, and the iteration loop against a running Docker agent. Use this when starting or restarting the dev Electron app, or after changing any minds component (mngr, the system interface, the default workspace template). |
Minds Dev Workflow
This skill covers the full minds dev cycle: standing up a DEFAULT_WORKSPACE_TEMPLATE worktree, syncing the live mngr code into that worktree's system/vendor/mngr/, activating a per-developer dev env, starting the dev Electron app, and iterating against a running Docker agent. Use it whenever you're about to start the dev app (the system/vendor/mngr sync needs to happen every startup) or after editing any component (mngr, the system_interface, the default workspace template).
Architecture Overview
The minds stack has four components that need to stay in sync:
- minds desktop client (
apps/minds/) -- Electron app + FastAPI backend that runs locally, proxies to agent web servers
- system_interface (lives in
default-workspace-template/system/libs/system_interface/, distributed as the system-interface CLI) -- FastAPI + web UI that runs INSIDE the agent's Docker container as a background service
- mngr core (
libs/mngr/) -- the agent management CLI
- default-workspace-template -- the template repo that defines the Docker container (Dockerfile, services.toml, skills, scripts)
The template contains a system/vendor/mngr/ directory (a snapshot of the mngr repo). During development, we sidestep that snapshot by rsyncing the local mngr working tree directly into a parallel-named branch of a DEFAULT_WORKSPACE_TEMPLATE worktree under .external_worktrees/default-workspace-template/.
How changes propagate
local mngr repo --> DEFAULT_WORKSPACE_TEMPLATE worktree's system/vendor/mngr/ --> Docker container's /home/user/workspace/
(under .external_worktrees/) (via rsync over SSH)
The desktop client runs on the host (via Electron). The system interface + mngr run inside the container. The system/vendor/mngr/ sync is what makes the dev loop work end-to-end.
Critical: the system/vendor/mngr/ sync must happen BEFORE every Create
When you click "Create" in the desktop client with a LOCAL-Docker provider, the desktop client (apps/minds/.../agent_creator.py) takes whatever's currently in the DEFAULT_WORKSPACE_TEMPLATE worktree (including system/vendor/mngr/), shallow-clones it to a temp dir, rsyncs the worktree's working dir over the clone (so uncommitted default-workspace-template-side changes propagate), and ships the result into /home/user/workspace/ in the Docker container. mngr inside the container is uv tool install -e'd from /home/user/workspace/system/vendor/mngr/.
The desktop client does NOT auto-sync live mngr code into the worktree. If system/vendor/mngr/ is stale relative to your live mngr working tree, the Docker container's mngr will be stale too -- and (depending on what you've changed) mngr create inside the container may reject your .mngr/settings.toml with errors like Unknown fields in agent_types.claude: [...]. The bootstrap's chat-agent-create step then fails, and you'll see an empty workspace with "No conversation data" in the chat panel.
just minds-start (the all-in-one recipe described below) does this sync for you on every invocation. Use it rather than running pnpm start directly when you're testing local mngr changes.
Quick start (first time and every time)
First time on this machine? Install the one-time prerequisites in
apps/minds/docs/dev-setup.md first (Docker, Node/pnpm, GNU rsync, GitHub
access, Vault login, Modal profile) -- the steps below assume they're in place.
cd apps/minds && pnpm install && cd ../..
just default-workspace-template-worktree
vault login -method=oidc
eval "$(uv run minds-admin env activate --create --deploy dev-<your-user>)"
uv run minds-admin deploy
just minds-start
That's it. After the create-form is filled in and you've created an agent, see Iterating on a running agent for the inner loop.
If you want to run against prod / staging instead of a personal dev env, use eval "$(uv run minds-admin env activate production)" (or ... activate staging) and then just minds-start. Do not run minds-admin env deploy against production / staging without coordinating with the rest of the team -- that pushes Vault secrets to Modal and re-deploys the live tier; the unified deploy CLI requires --yes-i-mean-production / --yes-i-mean-staging as a safety bar.
What just minds-start does
- Verifies a minds env is activated in the shell (refuses with a helpful error if not).
- Verifies the DEFAULT_WORKSPACE_TEMPLATE worktree exists at
.external_worktrees/default-workspace-template/ and bails with a helpful error if not.
- Rsyncs the live mngr working tree into the DEFAULT_WORKSPACE_TEMPLATE worktree's
system/vendor/mngr/ using the same exclusions as the pool-bake's --mngr-source path (.git, __pycache__, .venv, node_modules, etc.). Uncommitted changes are included; nothing is committed in the DEFAULT_WORKSPACE_TEMPLATE worktree.
- Launches Electron with the right
MINDS_WORKSPACE_* env vars so the create-form auto-fills "repository" and "branch". The workspace name is not prefilled -- the form generates a mind-N name unless you type one into its advanced "Name" field.
Iterating on a running agent
After making changes to any component (mngr, the template's system_interface, the template, etc.), sync them into a running agent's container:
eval "$(uv run minds-admin env activate dev-<your-user>)"
apps/minds/scripts/propagate_changes \
--user root --host 127.0.0.1 --port <SSH_PORT> \
--key <SSH_KEY_PATH>
This:
- Verifies a minds env is activated in the shell (refuses without it).
- Rsyncs the mngr repo into the DEFAULT_WORKSPACE_TEMPLATE worktree's
system/vendor/mngr/ (same step just minds-start does, idempotent)
- Stops the agent (
mngr stop)
- Rsyncs the full template (with updated system/vendor/mngr/) into
/home/user/workspace/ in the container
- Rebuilds the system interface frontend (
npm run build via SSH)
- Starts the agent (
mngr start)
- Stops and restarts the Electron desktop client (clean SIGTERM shutdown)
The whole cycle takes about 5-10 seconds.
For local (non-container) agents:
eval "$(uv run minds-admin env activate dev-<your-user>)"
apps/minds/scripts/propagate_changes --target /path/to/agent/workdir
Find the Docker container's SSH port and key
The port is randomly assigned by Docker per agent. The container name is <MNGR_PREFIX><agent-name>-host (set by your activated env's MNGR_PREFIX):
eval "$(uv run minds-admin env activate dev-<your-user>)"
docker ps --format '{{.Names}} {{.Ports}}' | grep "${MNGR_PREFIX}mind-"
The SSH key for a minds Docker agent lives under the activated env's MNGR_HOST_DIR:
eval "$(uv run minds-admin env activate dev-<your-user>)"
find "${MNGR_HOST_DIR}/profiles" -path "*/docker/*/keys/docker_ssh_key"
Do NOT use a key from ~/.mngr/profiles/... -- that belongs to non-minds mngr agents and will silently fail with "Permission denied (publickey)". Likewise do NOT use a key from a different activated env (each env has its own profile dir).
Reference
Just recipes that touch this stack
| Recipe | Purpose |
|---|
just minds-start | Preferred dev entry point. Sync live mngr -> DEFAULT_WORKSPACE_TEMPLATE system/vendor/mngr, then launch the Electron app. Requires an activated minds env in the shell. |
just minds-stop | Kill the desktop client started in this worktree by just minds-start. |
just minds-build | Build the desktop client distributable via todesktop (slow, only for releases). |
apps/minds/scripts/propagate_changes ... | Sync changes into a running container without restarting the Electron app from scratch. See "Iterating on a running agent". Requires an activated env. |
minds-admin pool create --mngr-source <monorepo-root> ... | Bake an OVH pool host (the imbue_cloud pool's VPS provider). --mngr-source rsyncs the monorepo into the DEFAULT_WORKSPACE_TEMPLATE system/vendor/mngr/ for the duration of the bake. (For pool hosts only -- has no effect on Docker mode.) Requires an activated env, from which it resolves the pool-ssh credentials via Vault (typically driven via the just pool-bake recipes). |
just services-deploy [--yes-i-mean-<tier>] | Run minds-admin env deploy on the activated env. For dev envs: provisions Modal env / Neon / SuperTokens + deploys both Modal apps + writes ~/.minds-<env>/{client.toml,secrets.toml}. For tier deploys: pushes Vault secrets to Modal + deploys both Modal apps, no local state written. |
just sync-vendor-mngr-live [default-workspace-template-path] | Rsync the live mngr working tree (uncommitted changes included) into DEFAULT_WORKSPACE_TEMPLATE's system/vendor/mngr/, no commit. This is the sync just minds-start runs at launch; run it directly to re-sync mid-session without relaunching. |
just sync-vendor-mngr <default-workspace-template-path> | One-shot: snapshot mngr HEAD into DEFAULT_WORKSPACE_TEMPLATE's system/vendor/mngr/ via git archive and commit in DEFAULT_WORKSPACE_TEMPLATE. Use for "release" syncs, not dev iteration (it commits and only carries committed mngr content). |
Vault (for minds-admin env deploy and pool / slice bakes)
Both minds-admin env deploy (which reads dev-tier provisioning credentials -- Neon, SuperTokens, etc. -- at command time) and slice bakes (minds-admin pool create, just pool-bake / just pool-bake-from-worktree -- the tier's POOL_SSH_PRIVATE_KEY, the host-pool DSN, etc.) read secrets from HCP Vault. (Baking new OVH classic VPS pool hosts is deprecated and no longer supported.) Two things to know:
-
Login is interactive. Run vault login -method=oidc once per session (browser OIDC); the token lands at ~/.vault-token.
-
VAULT_ADDR / VAULT_NAMESPACE are usually NOT set in a non-interactive shell. The minds-admin commands (and the bake-* recipes that wrap them) apply the imbue HCP defaults automatically via apps/minds/imbue/minds/envs/vault_reader.py, so they "just work" with only the token -- prefer them. If you run a raw vault command, a bare vault defaults to https://127.0.0.1:8200 and fails with "connection refused" -- that is a missing address, NOT "logged out" (don't ask the operator to re-login, and don't ask them for VAULT_ADDR). Export the defaults first:
export VAULT_ADDR=https://vault-cluster-public-vault-df29b16f.9b573ab7.z1.hashicorp.cloud:8200
export VAULT_NAMESPACE=admin
Single source of truth: _DEFAULT_VAULT_ADDR / _DEFAULT_VAULT_NAMESPACE in vault_reader.py -- read them from there in case they drift.
Env vars just minds-start sets
MINDS_ROOT_NAME / MNGR_HOST_DIR / MNGR_PREFIX / MINDS_CLIENT_CONFIG_PATH come from minds-admin env activate <name> in your shell -- minds-start requires them to be set and refuses otherwise. Beyond those:
| Variable | Purpose | Default |
|---|
MINDS_WORKSPACE_GIT_URL | Template repo path/URL for the create-form | <repo>/.external_worktrees/default-workspace-template/ (create it with just default-workspace-template-worktree); just minds-start sets this. Absent it, workspace_defaults.py falls back to the default-workspace-template remote URL |
MINDS_WORKSPACE_BRANCH | Default git branch for the template | The DEFAULT_WORKSPACE_TEMPLATE path's current branch (matches your mngr branch when you set up the worktree on a parallel-named branch) |
The desktop client reads these in apps/minds/imbue/minds/desktop_client/workspace_defaults.py.
Clean shutdown
The Electron app shuts down cleanly via this chain:
- Electron window close ->
before-quit handler -> backend.js shutdown() -> SIGTERM to uv run
uv run forwards SIGTERM to Python
- The signal handler in
serve_desktop_client (desktop_client/server.py) flips
shutdown_event, wakes the SSE and /ui/ws handlers, and stops the cheroot
WSGI server
- The runtime's
finally runs _shutdown_desktop_client: the ordered teardown
(envelope stream consumer / mngr forward, permission-requests consumer, SSH
tunnels, sync scheduler, pre-warmed mngr caller), then triggers and exits the
root ConcurrencyGroup
- Process exits with code 143 within a few seconds
Three processes intentionally SURVIVE a shutdown: the detached mngr latchkey forward supervisor and its latchkey gateway + mngr observe --discovery-only children (see the spawn comment in cli/run.py). They keep
agent tunnels working across desktop-client restarts and are adopted, not
respawned, by the next launch; just minds-stop excludes them (they run in
their own session) and they are not orphans.
If the chain breaks in other ways (orphaned mngr event readers, a
"strands did not finish in time" warning in minds-events.jsonl, or
minds-stop reporting force-killed leftovers), something is wrong --
investigate, do not just kill the orphans.
Rsync exclusions
just sync-vendor-mngr-live (which just minds-start calls), minds-admin pool create --mngr-source ..., and propagate_changes all rsync into system/vendor/mngr/ using one shared form (rsync -a --delete --filter=':- .gitignore' --exclude=.git --exclude=uv.lock). The form, the rationale for each exclude, and the source-of-truth constants live in apps/minds/docs/vendor-mngr-sync.md.
propagate_changes additionally protects data/, .mngr/, and .claude/settings.local.json from deletion when rsyncing into /home/user/workspace/.
Editable installs
The DEFAULT_WORKSPACE_TEMPLATE Docker build installs mngr (system/vendor/mngr/libs/mngr) and the system_interface (system/libs/system_interface/) editable via uv tool install -e, run by system/scripts/build_workspace.sh (which the Dockerfile invokes with RUN bash), so Python code changes in either location are picked up immediately after rsync. Frontend changes require the npm run build step (done automatically by propagate_changes).
Template settings
The template's .mngr/settings.toml controls agent types, create templates, env vars, and extra_window entries. Notable knobs:
disable_plugin = ["recursive", "ttyd"] -- disables plugins that conflict with template-managed services
extra_window entries for bootstrap, telegram, terminal, reviewer_settings
env entries for IS_SANDBOX, IS_AUTONOMOUS, and reviewer toggles
Logs
| Path | Contents |
|---|
/tmp/claude-*/.../tasks/<id>.output | Electron app stdout when launched via just minds-start (path printed at launch) |
~/.minds/logs/minds.log, ~/.minds/logs/minds-events.jsonl | Minds backend (production) |
~/.minds-<env-name>/logs/minds.log, ~/.minds-<env-name>/logs/minds-events.jsonl | Minds backend (per-env) |
Cleaning up the legacy ~/.devminds/
If you used the pre-refactor layout (~/.devminds/ for all dev iteration plus ~/.devminds/envs/<dev-name>.toml per-env overrides), that root is now obsolete. No migration script -- just rm -rf ~/.devminds/ when convenient. A stale MINDS_ROOT_NAME=devminds in a parent shell is silently treated as unset (with a warning); the in-shell minds-admin env activate <name> always wins.
Manual setup (fallback)
If a recipe is broken or you want to run something the recipes don't cover, here are the underlying steps the recipes wrap.
Create the DEFAULT_WORKSPACE_TEMPLATE worktree by hand
Normally just default-workspace-template-worktree does this for you (clones default_workspace_template into
.external_worktrees/default-workspace-template on the current mngr branch). To do it
by hand from your own default_workspace_template clone instead:
cd "${DEFAULT_WORKSPACE_TEMPLATE_DIR:-$HOME/project/default-workspace-template}"
git worktree add /path/to/mngr/worktree/.external_worktrees/default-workspace-template -b <branch-name> origin/main
Sync mngr code into the DEFAULT_WORKSPACE_TEMPLATE worktree's system/vendor/mngr/ on its own
just sync-vendor-mngr-live
This is the sync just minds-start runs at launch, so use it to re-sync mid-session without relaunching the app (the desktop client picks the new copy up on the next Create). minds-admin pool create --mngr-source ... does the same for the duration of the bake, and propagate_changes does it as step 1 on each iteration. The underlying form is:
rsync -a --delete \
--filter=':- .gitignore' \
--exclude=.git --exclude=uv.lock \
./ .external_worktrees/default-workspace-template/system/vendor/mngr/
Start electron by hand without the just recipe
eval "$(uv run minds-admin env activate dev-<your-user>)"
TEMPLATE_BRANCH=$(cd .external_worktrees/default-workspace-template && git branch --show-current)
(
set -a
source .env
set +a
export MINDS_WORKSPACE_GIT_URL="$(pwd)/.external_worktrees/default-workspace-template"
export MINDS_WORKSPACE_BRANCH="$TEMPLATE_BRANCH"
cd apps/minds && pnpm start
)