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.