Skill Index

mngr/

minds-dev-workflow

community[skill]

End-to-end dev workflow for the minds app stack -- first-time bring-up, every-startup 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 FCT template).

$/plugin install mngr

details

Minds Dev Workflow

This skill covers the full minds dev cycle: standing up an FCT worktree, syncing the live mngr code into that worktree's 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 vendor/mngr sync needs to happen every startup) or after editing any component (mngr, the system_interface, the FCT template).

Architecture Overview

The minds stack has four components that need to stay in sync:

  1. minds desktop client (apps/minds/) -- Electron app + FastAPI backend that runs locally, proxies to agent web servers
  2. system_interface (lives in forever-claude-template/apps/system_interface/, distributed as the system-interface CLI) -- FastAPI + web UI that runs INSIDE the agent's Docker container as a background service
  3. mngr core (libs/mngr/) -- the agent management CLI
  4. forever-claude-template -- the template repo that defines the Docker container (Dockerfile, services.toml, skills, scripts)

The template contains a 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 an FCT worktree under .external_worktrees/forever-claude-template/.

How changes propagate

local mngr repo  -->  FCT worktree's vendor/mngr/  -->  Docker container's /code/
                      (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 vendor/mngr/ sync is what makes the dev loop work end-to-end.

Critical: the 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 FCT worktree (including vendor/mngr/), shallow-clones it to a temp dir, rsyncs the worktree's working dir over the clone (so uncommitted FCT-side changes propagate), and ships the result into /code/ in the Docker container. mngr inside the container is uv tool install -e'd from /code/vendor/mngr/.

The desktop client does NOT auto-sync live mngr code into the worktree. If 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)

# 1. (Once) Install electron deps.
cd apps/minds && pnpm install && cd ../..

# 2. (Once) Stand up an FCT worktree at .external_worktrees/forever-claude-template/
#    on a branch named after your current mngr branch (so template-side
#    edits stay parallel-named). Required by `just minds-start`.
git -C ~/project/forever-claude-template worktree add \
    -b "$(git rev-parse --abbrev-ref HEAD)" \
    "$PWD/.external_worktrees/forever-claude-template" \
    josh/start-minds   # or another base branch / tag

# 3. (Once) Bootstrap your personal dev env. Pick a name like
#    "<your-user>-dev" (convention; any DevEnvName works). --create
#    idempotently mkdirs ~/.minds-<your-user>-dev/ if it doesn't exist.
eval "$(uv run minds env activate --create <your-user>-dev)"
uv run minds env deploy

# 4. (Every time you start the app, in a fresh shell) Activate the env
#    and run `just minds-start`. The recipe re-syncs live mngr ->
#    vendor/mngr/ and launches Electron.
eval "$(uv run minds env activate <your-user>-dev)"
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 env activate production)" (or ... activate staging) and then just minds-start. Do not run minds 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

  1. Verifies a minds env is activated in the shell (refuses with a helpful error if not).
  2. Verifies the FCT worktree exists at .external_worktrees/forever-claude-template/ and bails with a helpful error if not.
  3. Rsyncs the live mngr working tree into the FCT worktree's 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 FCT worktree.
  4. Launches Electron with the right MINDS_WORKSPACE_* env vars so the create-form auto-fills "repository", "name", and "branch".

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 env activate <your-user>-dev)"
apps/minds/scripts/propagate_changes \
  --user root --host 127.0.0.1 --port <SSH_PORT> \
  --key <SSH_KEY_PATH>

This:

  1. Verifies a minds env is activated in the shell (refuses without it).
  2. Rsyncs the mngr repo into the FCT worktree's vendor/mngr/ (same step just minds-start does, idempotent)
  3. Stops the agent (mngr stop)
  4. Rsyncs the full template (with updated vendor/mngr/) into /code/ in the container
  5. Rebuilds the system interface frontend (npm run build via SSH)
  6. Starts the agent (mngr start)
  7. 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 env activate <your-user>-dev)"
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 env activate <your-user>-dev)"   # so we know MNGR_PREFIX
docker ps --format '{{.Names}} {{.Ports}}' | grep "${MNGR_PREFIX}mindtest"
# e.g.  minds-<your-user>-dev-mindtest-host 0.0.0.0:32772->22/tcp

The SSH key for a minds Docker agent lives under the activated env's MNGR_HOST_DIR:

eval "$(uv run minds env activate <your-user>-dev)"   # exports MNGR_HOST_DIR
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

RecipePurpose
just minds-startPreferred dev entry point. Sync live mngr -> FCT vendor/mngr, then launch the Electron app. Requires an activated minds env in the shell.
just minds-stopKill the desktop client started in this worktree by just minds-start.
just minds-buildBuild 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.
mngr imbue_cloud admin pool create --mngr-source <monorepo-root> ...Bake a Vultr pool host. --mngr-source rsyncs the monorepo into the FCT vendor/mngr/ for the duration of the bake. (For pool hosts only -- has no effect on Docker mode.) Requires an activated env.
just deploy [--yes-i-mean-<tier>]Run minds 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 <fct-path>One-shot: snapshot mngr HEAD into FCT's vendor/mngr/ via git archive and commit in FCT. Use for "release" syncs, not dev iteration (it commits and only carries committed mngr content).

Env vars just minds-start sets

MINDS_ROOT_NAME / MNGR_HOST_DIR / MNGR_PREFIX / MINDS_CLIENT_CONFIG_PATH come from minds env activate <name> in your shell -- minds-start requires them to be set and refuses otherwise. Beyond those:

VariablePurposeDefault
MINDS_WORKSPACE_GIT_URLTemplate repo path/URL for the create-form<repo>/.external_worktrees/forever-claude-template/ if it exists, else ~/project/forever-claude-template
MINDS_WORKSPACE_NAMEDefault agent name in the create-formmindtest (override with agent_name=...)
MINDS_WORKSPACE_BRANCHDefault git branch for the templateThe FCT 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/templates.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
  • Uvicorn catches SIGTERM, does 1-second graceful shutdown (timeout_graceful_shutdown=1)
  • ASGI lifespan shutdown hook runs stream_manager.stop() (terminates mngr observe/mngr event subprocesses)
  • Uvicorn re-raises SIGTERM, process exits with code 143

If this chain breaks (orphaned mngr observe/mngr event processes appear), something is wrong -- investigate, do not just kill the orphans.

Rsync exclusions

just minds-start, mngr imbue_cloud admin pool create --mngr-source ..., and propagate_changes all share one form when rsyncing into vendor/mngr/:

rsync -a --delete --filter=':- .gitignore' --exclude=.git --exclude=uv.lock ...

--filter=':- .gitignore' is rsync's dir-merge filter: it reads .gitignore at each directory level under the source and applies its - (exclude) rules. That covers __pycache__, .venv, node_modules, .test_output, .mypy_cache, .ruff_cache, .pytest_cache, .external_worktrees, and anything else listed in the source repo's gitignore.

The two manual excludes are for things gitignore deliberately doesn't list:

  • .git -- gitignore never lists it (git's internal dir).
  • uv.lock -- intentionally committed at the mngr root, but each install context should regenerate its own.

propagate_changes additionally protects runtime/, .mngr/, and .claude/settings.local.json from deletion when rsyncing into /code/.

Editable installs

The Dockerfile uses uv tool install -e for mngr (vendored under vendor/mngr/) and for the system_interface (at apps/system_interface/), 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

PathContents
/tmp/claude-*/.../tasks/<id>.outputElectron app stdout when launched via just minds-start (path printed at launch)
~/.minds/logs/minds.log, ~/.minds/logs/minds-events.jsonlMinds backend (production)
~/.minds-<env-name>/logs/minds.log, ~/.minds-<env-name>/logs/minds-events.jsonlMinds 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 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 FCT worktree by hand

cd ~/project/forever-claude-template
git worktree add /path/to/mngr/worktree/.external_worktrees/forever-claude-template -b <branch-name> origin/main

Sync mngr code into the FCT worktree's vendor/mngr/ by hand

rsync -a --delete \
    --filter=':- .gitignore' \
    --exclude=.git --exclude=uv.lock \
    ./ .external_worktrees/forever-claude-template/vendor/mngr/

This is what just minds-start does internally, what mngr imbue_cloud admin pool create --mngr-source ... does for the duration of the bake, and what propagate_changes does as step 1 on each iteration.

Start electron by hand without the just recipe

eval "$(uv run minds env activate <your-user>-dev)"
TEMPLATE_BRANCH=$(cd .external_worktrees/forever-claude-template && git branch --show-current)
(
  set -a
  source .env
  set +a
  export MINDS_WORKSPACE_GIT_URL="$(pwd)/.external_worktrees/forever-claude-template"
  export MINDS_WORKSPACE_NAME="mindtest"
  export MINDS_WORKSPACE_BRANCH="$TEMPLATE_BRANCH"
  cd apps/minds && pnpm start
)

technical

github
imbue-ai/mngr
stars
374
license
NOASSERTION
contributors
26
last commit
2026-05-29T05:55:24Z
file
.claude/skills/minds-dev-workflow/SKILL.md

related