Skip to content

Stand Operations

The stand is a singleton live environment shared by the agent fleet. Agents coordinate access through a lease-backed state file, and coordd prepares the active lease by running an Ansible YAML profile.

A stand can be local (STAND_HOST=localhost) or remote (STAND_HOST as an SSH config alias or comma-separated aliases). The profile decides what "deploy" means for the project: a smoke probe, an rsync install, a Docker Compose restart, a Vite dev server, or any other Ansible-native deployment sequence. greatminds owns the lease/state/evidence protocol; the project owns the playbook content.

Project Environment

Put machine-local stand values in .greatminds/PROJECT.env:

STAND_HOST=localhost
STAND_USER=violet

PROJECT.env is sourced before agents start and passed to stand profiles as Ansible extra vars. In a profile, STAND_HOST is readable as {{ STAND_HOST }}, STAND_USER as {{ STAND_USER }}, and any custom key the project adds is available the same way.

For a remote stand:

STAND_HOST=app-stand
STAND_USER=deploy

app-stand should resolve through the OS user's SSH config, known hosts, and keys. Multi-node stands can use a comma list such as STAND_HOST=web-a,web-b.

Ansible Profiles

The project profile registry lives at:

coordination/stand-profiles.yaml

Stand playbooks live under:

coordination/stand-profiles/

The lease --profile value selects a registry key, not a raw filename. The registry maps that key to a YAML playbook file:

profiles:
  smoke-only:
    file: smoke-only.yaml
    purpose: Fast reachability and readiness check.
    environment: stand
    used_for: [deploy_only, warmup, quick_readiness]
    default_for: [verify_only]

For example, --profile smoke-only records active_lease.profile: smoke-only and runs the registry entry's file, usually coordination/stand-profiles/smoke-only.yaml. The same profile name can point at a differently named file, such as file: smoke.yaml, when a project wants that convention.

used_for is the capability list for the profile. default_for assigns common role intents such as feature_test, explorer, reviewer, live_developer, production_deploy, and production_review to a profile. The vocabulary is defined in the packaged schema copied to .greatminds/schema.yaml under stand_profile_registry. Each default_for token can belong to only one profile, so automated role selection is unambiguous.

Use the registry commands after edits:

greatminds stand profiles list
greatminds stand profiles doctor

coordd runs the profile with Ansible and injects:

  • every key from .greatminds/PROJECT.env;
  • the active lease id as lease_id;
  • the task id as task_id;
  • the isolated task worktree path as worktree.

A minimal smoke profile:

---
- name: register stand node
  hosts: localhost
  gather_facts: false
  tasks:
    - name: add configured stand host
      ansible.builtin.add_host:
        name: "{{ STAND_HOST | default('localhost') }}"
        groups: stand_nodes
        ansible_connection: >-
          {{ 'local' if (STAND_HOST | default('localhost')) == 'localhost'
             else 'ssh' }}

- name: smoke stand
  hosts: stand_nodes
  gather_facts: false
  tasks:
    - name: remote shell works
      ansible.builtin.command: /bin/true
      changed_when: false

For a deploy profile, add tasks that copy or build from {{ worktree }} and then run readiness probes. The shipped full-deploy.yaml, vite-dev.yaml, and smoke-only.yaml files are reference profiles, not hidden deployment logic. Register additional project profiles in coordination/stand-profiles.yaml.

Profiles that start long-lived processes should also include cleanup tasks tagged teardown. Greatminds runs those tasks best-effort when a lease is released, reclaimed, manually downed, recovered with stand up, or fails during deploy. This is the profile-owned place to stop dev servers, containers, and ports that the deploy started. The shipped vite-dev.yaml includes a teardown task that stops the saved Vite pid and clears any older orphan process still holding the configured port. Existing projects with an unedited shipped vite-dev.yaml are refreshed by greatminds migrate / greatminds update. For vite-dev leases, Greatminds also runs a narrow generic cleanup for the declared Vite port (VITE_DEV_PORT, vite_port, or the profile default) so orphan processes on that declared port can be cleared even when profile-tagged teardown did not run. When the stand is already free and no lease owns it, coordd runs the declared-port cleanup on startup and on the stand-free periodic hook. Before deploying any non-vite-dev profile, Greatminds also clears that declared Vite port so a leftover live-UI server cannot block a packaged UI bind on the same port. Cleanup targets every stand host declared through STAND_HOST or STAND_HOST_* (STAND_HOST_A, STAND_HOST_B, and so on), so remote multi-node fleets do not accidentally clean localhost while the dev server is still running on the stand node.

Before a stand profile is loaded from a lease worktree, Greatminds refreshes that task worktree from the current worktrees.default_branch when the path is a real git worktree. This lets old in-flight tasks pick up committed infrastructure or profile fixes from the base branch without copying those files into the task overlay.

For a production deployment or post-deploy review, create a profile with:

profiles:
  production:
    file: production.yaml
    purpose: Production deployment and post-deploy verification.
    environment: production
    requires_explicit_user_approval: true
    allowed_roles: [ARCHITECT-REVIEWER, MAINTAINER]
    used_for: [production_deploy, production_post_deploy_review]
    default_for: [production_deploy, production_review]

When a product task moves from feature_review/ to verified/, Greatminds looks for the profile whose registry entry includes default_for: [production_deploy]. If one exists, coordd creates an internal system lifecycle lease (holder_role: COORDD) from the current merged project tree and runs that profile. This is not agent/model work and does not file an inbox task. On deploy success the system lease auto-releases, returning the singleton to free or promoting the next queued lease.

The CLI still enforces allowed_roles and requires an explicit approval marker for manually requested production leases:

greatminds stand lease \
  --task <task-id> \
  --profile production \
  --profile-approval USER_APPROVED

Live or development profiles that displace a public upstream can declare the profile that restores it:

profiles:
  vite-dev:
    file: vite-dev.yaml
    used_for: [live_ui]
    default_for: [live_developer]
    restore_profile: production

On release, reclaim, or deploy failure of that live profile, Greatminds queues a front-of-line system restore lease for the named profile. The restore lease also auto-releases after successful deploy.

State File

The source of truth is:

.greatminds/.stand/state.yaml

greatminds stand status reads that file and prints the current state, active lease, pending FIFO queue, and recent transition history. Use the CLI output as the source of truth; do not inspect or edit state.yaml directly.

The stand has four states:

State Meaning
free No active lease. The next lease can be granted immediately.
preparing coordd is deploying or restarting the stand for the active lease.
ready The stand is ready for the lease holder to run probes.
down Stand operation is paused because deploy or infrastructure recovery is needed.

Each active lease carries a lease_id, task id, worktree path, profile name, profile file, holder role, grant timestamp, optional ready timestamp, and TTL. Pending leases wait in FIFO order in the same state file.

Lease Flow

The role that needs a live stand requests a lease:

greatminds stand lease --task <task-id> --worktree <path> --profile <profile>

--worktree must point at the task's isolated worktree under <project>/.worktrees/, not the main fleet checkout. The usual form is:

greatminds stand lease \
  --task <task-id> \
  --worktree "$(greatminds worktree path <task-id>)" \
  --profile full-deploy

If the stand is free, the lease becomes active and the state moves to preparing. If another lease is active, the new lease is appended to the FIFO queue. The command prints the lease_id; the requester must keep that token.

coordd watches .greatminds/.stand/state.yaml and runs the active lease profile file selected by active_lease.profile. Operators can inspect progress with:

greatminds stand status

When state is preparing, coordd deploys from the lease worktree using the lease profile. On success it records a deploy marker and transitions the stand to ready, equivalent to:

greatminds stand ready --lease-id <lease-id>

That moves the state to ready and files an inbox-info message to the holder: stand lease <lease-id> ready; task=<task-id>. The ready transition is valid only after the configured stand profile has run and left its deploy marker.

If the deploy profile exits non-zero, Greatminds fails that lease, records the full ansible output under .greatminds/.stand/deploy-<lease-id>.log, stores a compact last_deploy_failure summary, runs teardown best-effort, and returns the singleton stand to free so unrelated queued work can continue. The failure is sent to the lease holder through inbox info; the holder decides whether to fix and re-lease the task. When no queued lease is promoted, Greatminds also emits an explicit stand-available event so coordd re-scans stand-dependent queues instead of waiting for a new task-file move.

Queued work promotion after a deploy failure is poison-aware: a queued lease with the same task, profile, and worktree as the failed lease is not immediately promoted again. That lets unrelated queued leases advance instead of looping the same failing deploy on the singleton stand.

The holder, usually TESTER or EXPLORER, then runs its own probes against the prepared stand and releases the lease:

greatminds stand release --lease-id <lease-id> --result pass|fail|partial

Only the lease holder can release an active lease. Releasing moves the stand back to free; coordd promotes the next queued lease on a later tick.

TTL And Recovery

Leases have a default TTL from the packaged schema copied to .greatminds/schema.yaml (stand.resource.lease), currently four hours, with a warning window before automatic release. The TTL is a safety valve for abandoned leases; it is not a substitute for explicitly releasing a lease after probes finish.

If the stand has an infrastructure incident that is not scoped to one lease, the operator or maintainer runs:

greatminds stand down --reason "<operational reason>"

down pauses queue processing for global recovery. After recovery:

greatminds stand up --reason "<recovery note>"

That returns the stand to free so queued work can resume. coordd treats the stand becoming free as a dispatch event and re-scans stand-dependent queues, so tasks already parked in feature_test or review queues do not require a manual wake after recovery.

Evidence Boundary

The coordd deploy step only proves infrastructure readiness: process status, health endpoint availability, remote reachability, GPU availability when requested, and equivalent bring-up checks. It does not run product acceptance tests.

Functional verification belongs to the lease holder. For stand-required tasks, TESTER records the lease evidence in the product task's tests block, including stand_evidence.lease_id, result, commit, worktree fingerprint, and its own functional_probes plus stand_evidence.tester_observations. greatminds gate-check <task-id> reads that tests-block evidence first.

Use the lease commands for all stand operations. Stand readiness belongs in .greatminds/.stand/state.yaml, and product validation evidence belongs in the task's tests block.