Skip to content

Transitions

Allowed transitions are encoded in the packaged schema copied to .greatminds/schema.yaml in an installed project. The CLI enforces the current role, current queue, target queue, and required readiness evidence.

verified/ is not an absolute sink. If later review finds that verified work is wrong, invalid, or already reverted, ARCHITECT-REVIEWER can append a rollback block with a non-empty reason and move the task from verified/ to either archive/ or feature_review/. Use archive/ for withdrawn work whose code state already matches the rollback; use feature_review/ when the task needs another amendment or review cycle.

Read the source schema when changing the workflow:

schema.yaml on GitHub

# Coordination pipeline schema
#
# Machine-readable source of truth for queues, roles, transitions, and stand
# profiles. Read by the greatminds CLI (gate-check, wake-check, watchdog,
# lint-tokens), by each agent at tick start, and by humans cross-referencing
# COORDINATE.md prose.
#
# Invariants in COORDINATE.md must match what's encoded here. If they drift,
# the schema is normative — fix the prose, not the schema.

version: 1

# ---------------------------------------------------------------------------
# Glossary
#
# Single home for the text definition of every token used elsewhere in
# this file. A section that references a token (roles, queues, steps,
# enums, …) carries ONLY the token; its meaning is defined here, once.
# Agents read schema.yaml directly and resolve tokens against this
# glossary — there is no separate render step.
# ---------------------------------------------------------------------------
glossary:
  roles:
    ARCHITECT-PLANNER: "Intake triage, planning (writes the plan block, sets stand_required), and per-scope routing from feature_plan to feature_dev / feature_ui_dev / feature_docs. Interactive chat partner for the USER. Writes review_sessions but does not claim from it  EXPLORER owns review-session iteration."
    ARCHITECT-REVIEWER: "Final review with stand-evidence verification (gate_check result in the tests block), commit, and push if policy allows. Owns feature_blocked wake-up: runs greatminds wake-check at the start of each tick."
    DEVELOPER: "Backend / product-code implementation. Claims from feature_dev/ (placed there by ARCHITECT-PLANNER routing scope:backend tasks)."
    UI-DEVELOPER: "UI implementation. Claims from feature_ui_dev/ (placed there by ARCHITECT-PLANNER routing scope:ui tasks), writes the implementation block, hands off to feature_test/."
    LIVE-DEVELOPER: "Interactive, USER-paced developer for a single task on a leased stand (scenario C and ad-hoc live work). Claims from feature_live/, holds a stand lease and deploys to it during the session, chats live with the USER, and on USER approval hands to feature_review/ as a sprint task (REVIEWER no-regression review, no TESTER)."
    TECHNICAL-WRITER: "Documentation implementation. Claims from feature_docs/ (placed there by ARCHITECT-PLANNER routing scope:docs tasks); hands off to feature_docs_review/."
    TESTER: "Validates implemented features on a deployed stand. Acquires the stand by lease, probes once it is ready, and runs greatminds gate-check before mv to feature_review/."
    READER: "Reads docs as a fresh user; validates clarity and accuracy; routes the result out of feature_docs_review/."
    EXPLORER: "Uses the live system on the stand as a real user (scenario B), whatever the stand's shape. Files bugs as kind:bugfix tasks in feature_inbox/."
    USER: "Writes user_feedback/ or initiates an ARCHITECT-PLANNER chat. Never creates implementation tasks directly."
    MAINTAINER: "System operator, self-loop watchdog. Handles infrastructure the product pipeline cannot self-heal: agent restart, dead-pid recovery, greatminds CLI + schema.yaml maintenance, cutover orchestration. Receives asks at inbox/maintainer/ from coordd and from product agents when an issue is structural/schema rather than product. Sweeps PROACTIVELY every tick (an empty inbox is NOT 'nothing to do'): reclaims stale stand leases past TTL with a dead/absent holder, restarts dead agents and coordd, and escalates stalled task/queue chains to ARCHITECT-PLANNER. Does NOT claim product queues, triage user_feedback, or write plans."
  lifecycles:
    interactive: "Human-paced chat in a tmux pane; the USER drives each turn. coordd never runs its turns. The agent waits for USER input between turns  it does not self-schedule."
    self-loop: "Runs its own loop in a tmux pane. After each tick it MUST re-arm its next wake (sleep / ScheduleWakeup) for roles.<ROLE>.self_loop_wake_seconds (default 3600s = 1h) so it ticks again on that cadence  a shorter delay is allowed ONLY while actively mid-recovery, then return to the configured cadence; the agent must NOT invent a faster interval. coordd may nudge it (tmux_send_keys) but never runs its turns."
    driven: "No tmux pane; coordd runs each turn as a managed subprocess on queue / inbox / .stand events, holding the per-role run-lock for the full turn. Claude and Codex use stateful drivers; Cursor, Cline, Gemini, and OpenHands use one-shot headless subprocess drivers. The turn MUST return promptly  NEVER sleep / ScheduleWakeup / loop, or the run-lock never releases and the hang detector flags the turn."
  wake_mechanisms:
    tmux_send_keys: "Wake by typing WAKE_TEXT then Enter into the role's tmux pane. The only safe mechanism for interactive / self-loop roles (SIGINT to a live TUI is Ctrl-C  quit); also claude's default."
    sigint_deepest_descendant: "Wake a loop agent blocked on a bash sleep by sending SIGINT to its deepest descendant (the sleep). Safe only when that descendant is not a live TUI  never for interactive / self-loop."
  queue_kinds:
    active: "Normal hand-off queue: the owner claims tasks and moves them out per the transitions table."
    parking: "Holding queue for tasks waiting on explicit dependencies; re-activated by a wake-up, not claimed in the normal flow."
    terminal: "End state (e.g. verified, archive); tasks are not moved out."
    state: "Watch-only directory for a state / lease file, not tasks. coordd watches it to wake the owner on changes; nothing is claimed from it."
  queues:
    feature_inbox: "Unplanned product work and bug reports (from EXPLORER)."
    feature_plan: "Planned product work; PLANNER reviews and routes by scope to feature_dev / feature_ui_dev / feature_docs."
    feature_blocked: "Tasks parked on explicit dependencies; ARCHITECT-REVIEWER runs the wake-up."
    feature_dev: "Backend implementation."
    feature_ui_dev: "UI implementation."
    feature_docs: "Documentation implementation."
    feature_test: "Implemented code ready for TESTER validation on a deployed stand. Two entry paths: from an implementer queue (feature_dev / feature_ui_dev with an implementation block), or directly from feature_plan when plan.verify_only  a no-code stand/playbook verification task TESTER runs on a leased stand (no implementer step)."
    feature_docs_review: "Docs ready for READER review."
    feature_live: "Interactive single-task work for LIVE-DEVELOPER (routed by PLANNER when plan.interactive). The role leases a stand, works live with the USER, and hands to feature_review as a sprint task on USER approval."
    feature_review: "Final review; ARCHITECT-REVIEWER approves or hands back."
    verified: "Approved and committed product work."
    archive: "Duplicate, abandoned, superseded, or triaged-away product work."
    user_feedback: "Product feedback awaiting PLANNER triage."
    review_sessions: "Scenario-B review sessions; EXPLORER iterates live-stand exploration and files bugs to feature_inbox."
    .stand: "Singleton stand resource state.yaml + lease bookkeeping; coordd watches it and runs the deploy itself on lease lifecycle events."
  stand_profiles:
    full-deploy: "Full backend deployment on the target hosts with service health checks; scenarios A and B."
    vite-dev: "Backend deployed plus a Vite UI dev server with HMR (UI changes reload, no per-change redeploy); scenario C."
    smoke-only: "Fast reachability + CLI-responds probe  no rsync, no install. Warmup leases and post-deploy liveness checks."
  stand_probes:
    # Deploy/readiness checks (allowed) — GET / observe only.
    docker_ps: "Container liveness via docker ps / docker compose ps."
    http_health_get_200: "GET the health endpoint and require HTTP 200  the one allowed status check."
    ssh_reachable: "SSH reachability of the stand host plus a remote docker ps."
    gpu_check: "GPU availability via nvidia-smi, when the profile requests it."
    endpoint_presence: "Presence of an endpoint / bundle / bootstrap artifact  existence only, never exercising it."
    get_only_route_status: "GET-only HTTP status on routes (e.g. 200 on SPA paths); no POST, no body validation."
    # Forbidden for deploy/readiness — TESTER / EXPLORER territory.
    any_post: "ANY POST to the product API  zero exceptions (auth, bootstrap, login, business endpoints all included). Readiness is GET / observe only."
    response_body_validation: "Checking response body shape / fields / counts / status beyond the one allowed health 200."
    acceptance_criteria: "Running acceptance / regression criteria (layer-1 pass, AC checks, dedup re-POST, error paths)."
    followup_bug_filing: "Filing follow-up bugs / product gaps from readiness checks  triage is ARCHITECT-PLANNER's (note observations only)."
    e2e_browser_flow: "Full end-to-end browser / Playwright product flows."
    regression_scenario: "Regression scenarios that EXPLORER would run."
    gate_check: "greatminds gate-check  TESTER-only; reads TESTER's tests-block lease evidence."
  intake_disciplines:
    propose_then_file: "Present design / options / scope in chat first; WAIT for an explicit USER approval marker (ok / yes / file / go / do it, or a chosen option) before any FSM mutation (task new / plan / mv). Read-only ops (inbox list, queue inspection, gate-check) need no approval. Exception: a standing USER instruction that explicitly authorizes auto-filing within a scope."

# ---------------------------------------------------------------------------
# Queues
#
# Each queue is a directory under .greatminds/. Field meanings:
#
#   owner    — role responsible for the queue's content AND the single
#              role that claims from it (moves files out). Location =
#              ownership; content fields (scope, etc.) never determine
#              ownership.
#   writers  — roles allowed to write into the queue (own files + add
#              blocks to files they own).
#   kind     — a glossary.queue_kinds token (active / parking /
#              terminal / state).
#
# Authoritative source for OUTGOING moves is the `transitions:` table
# (the `by:` field there names the role allowed to perform each move).
# ---------------------------------------------------------------------------
# Per-queue purpose lives in glossary.queues; `kind` values in
# glossary.queue_kinds. Entries below are pure structure.
queues:
  feature_inbox:
    owner: ARCHITECT-PLANNER
    writers: [ARCHITECT-PLANNER, EXPLORER]
    kind: active
  feature_plan:
    owner: ARCHITECT-PLANNER
    writers: [ARCHITECT-PLANNER]
    kind: active
  feature_blocked:
    owner: ARCHITECT-REVIEWER
    writers: [DEVELOPER, UI-DEVELOPER, TECHNICAL-WRITER, TESTER, READER, ARCHITECT-REVIEWER, ARCHITECT-PLANNER]
    kind: parking
  feature_dev:
    owner: DEVELOPER
    writers: [DEVELOPER]
    kind: active
  feature_ui_dev:
    owner: UI-DEVELOPER
    writers: [UI-DEVELOPER]
    kind: active
  feature_docs:
    owner: TECHNICAL-WRITER
    writers: [TECHNICAL-WRITER]
    kind: active
  feature_test:
    owner: TESTER
    writers: [DEVELOPER, UI-DEVELOPER, TESTER]
    kind: active
  feature_docs_review:
    owner: READER
    writers: [TECHNICAL-WRITER, READER]
    kind: active
  feature_live:
    owner: LIVE-DEVELOPER
    writers: [LIVE-DEVELOPER, ARCHITECT-PLANNER]
    kind: active
  feature_review:
    owner: ARCHITECT-REVIEWER
    writers: [TESTER, READER, LIVE-DEVELOPER, ARCHITECT-REVIEWER]
    kind: active
  verified:
    owner: ARCHITECT-REVIEWER
    writers: [ARCHITECT-REVIEWER]
    kind: terminal
  archive:
    owner: ARCHITECT-PLANNER
    writers: [ARCHITECT-PLANNER, ARCHITECT-REVIEWER]
    kind: terminal
  user_feedback:
    owner: ARCHITECT-PLANNER
    writers: [USER]
    kind: active
  review_sessions:
    owner: EXPLORER
    writers: [ARCHITECT-PLANNER, EXPLORER]
    kind: active
  # .stand is a state-kind watch path, NOT a claim-from queue. coordd
  # watches state.yaml and runs the deploy itself on a `preparing` lease.
  .stand:
    writers: [TESTER, ARCHITECT-PLANNER, MAINTAINER, LIVE-DEVELOPER, EXPLORER, ARCHITECT-REVIEWER]
    kind: state

# ---------------------------------------------------------------------------
# Roles
#
# Each role lists its category, lifecycle, and the queues it may claim
# from. `lifecycle` is a glossary.lifecycles token (how the agent is
# driven); launch mechanics = f(lifecycle, coord.yaml tool).
# ---------------------------------------------------------------------------
# Per-role machine-readable contracts.
#
# Each role declares:
#   - responsibilities: list[str]      — high-level duties.
#   - forbidden_actions: list[str]     — things the role MUST NOT do.
#   - event_triggers: map[event → list[step_name]]
#                                     — ordered steps the role takes
#                                       on each event.
#
# The keys are intentionally short identifiers (snake_case verbs);
# they don't carry executable semantics — they're a checklist the
# LLM-driven agents follow + a regression-net against role drift.
# Step IDs that match CLI commands (``stand lease``,
# ``greatminds task mv``, etc.) hint at the runtime path; arbitrary
# verbs (``probe_via_ssh``) describe the logical step the LLM owns.
roles:
  ARCHITECT-PLANNER:
    category: product
    # lifecycle: a glossary.lifecycles token (orthogonal to coord.yaml's
    # `tool`; launch mechanics = f(lifecycle, tool)).
    lifecycle: interactive
    claims_from: [user_feedback, feature_inbox, feature_plan]
    # USER-interaction discipline (def: glossary.intake_disciplines).
    intake_discipline: propose_then_file
    responsibilities:
      - triage_user_feedback
      - write_plan_blocks
      - route_tasks_per_scope
      - chat_partner_for_user
      - file_review_sessions
      # PLANNER must know the stand-profile mechanism out of the box.
      - coordinate_stand_profile_tasks_via_registry_and_canon
      - file_profile_registry_task_on_unregistered_profile
    forbidden_actions:
      - implement_code
      - run_ansible
      - mark_stand_ready
      - claim_review_sessions
      # Exception: a scope:release UMBRELLA already decomposed into its
      # implementer sub-task(s) MAY be closed to archive — it is a tracking
      # gate, not in-flight work. Otherwise a release umbrella dead-ends in
      # feature_plan (no implementer route AND no close path).
      - archive_in_lifecycle_work_directly_except_decomposed_release_umbrella
    event_triggers:
      on_user_feedback_claim:
        - read_user_feedback_block
        - append_triage_block
        - mv_user_feedback_to_feature_inbox_or_archive
      on_feature_inbox_claim:
        - append_triage_block
        - mv_to_feature_plan_or_archive
      on_feature_plan_claim:
        - write_plan_block
        - set_stand_required
        - route_to_feature_dev_or_feature_ui_dev_or_feature_docs
        # scope:release is an umbrella GATE, not implementer work — there is
        # no single implementer queue for it. Decompose it into the concrete
        # implementer sub-task(s) that perform the release (packaging / smoke
        # / push), then close the umbrella to archive once its children are
        # verified. Without this a scope:release task parks forever in
        # feature_plan with no route and no close path.
        - decompose_scope_release_umbrella_into_subtasks_then_archive_umbrella
      # stand_down classifiers — PLANNER reads coordd's auto-notification
      # and routes the fix per the down_reason signature.
      on_stand_down_yaml_playbook_error:
        - read_down_reason_from_inbox_info
        - file_bugfix_task_for_coordination_stand_profiles_yaml
        - assign_to_developer
      on_stand_unregistered_profile:
        - read_registry_error
        - file_feature_dev_task_for_stand_profile_registry_entry
        - include_required_profile_name_file_used_for_default_for
  ARCHITECT-REVIEWER:
    category: product
    lifecycle: driven
    claims_from: [feature_review, feature_blocked]
    responsibilities:
      - verify_evidence_chain
      - commit_and_merge
      - resume_blocked_tasks_via_wake_check
      - rollback_verified_tasks
    forbidden_actions:
      - implement_code
      - claim_outside_review_queues
      - approve_without_gate_check_pass_when_stand_required
    event_triggers:
      on_feature_review_claim:
        - verify_blocks_chain
        - run_gate_check
        - append_review_block_approved_or_changes_requested
        - mv_to_verified_on_approved
        - mv_back_to_implementer_queue_on_changes_requested
      on_feature_blocked_claim:
        - run_wake_check
        - mv_to_resume_to_queue_when_dependencies_satisfied
        - archive_when_withdrawn_reason_present
      on_verified_rollback:
        - append_rollback_block_with_reason
        - mv_to_archive_or_feature_review
    stand_profile_selection:
      required_for:
        - final_review_when_stand_required
        - production_review
      default_for:
        final_review_when_stand_required: reviewer
        production_review: production_review
      allowed_used_for:
        - reviewer_validation
        - tester_validation
        - explorer_review
        - production_post_deploy_review
      must_select_from: coordination/stand-profiles.yaml
  DEVELOPER:
    category: product
    lifecycle: driven
    claims_from: [feature_dev]
    responsibilities:
      - implement_backend_features
      - write_unit_tests
      - file_implementation_block_with_files_list
    forbidden_actions:
      - claim_ui_or_docs_or_stand_tasks
      - deploy_stand
      - run_product_validation_on_stand
      - read_or_move_from_feature_plan
      - commit_to_main_directly
    event_triggers:
      on_feature_dev_claim:
        - implement_code_in_worktree
        - run_pytest_suite
        - append_implementation_block
        - mv_to_feature_test
  UI-DEVELOPER:
    category: product
    lifecycle: driven
    claims_from: [feature_ui_dev]
    responsibilities:
      - implement_ui_features
      - run_ui_tests
      - file_implementation_block
    forbidden_actions:
      - claim_backend_or_docs_tasks
      - deploy_stand
      - commit_to_main_directly
    event_triggers:
      on_feature_ui_dev_claim:
        - implement_ui_in_worktree
        - run_ui_test_suite
        - append_implementation_block
        - mv_to_feature_test
  LIVE-DEVELOPER:
    category: product
    # Interactive, USER-paced: works a single task live with the USER on
    # a leased stand. As the lease HOLDER it may deploy to its own stand
    # during the session (deploy_to_own_leased_stand_during_session) —
    # the only relaxation of centralized deploy ownership, scoped to
    # an active interactive lease. On USER "ok" it hands to feature_review
    # as a sprint task (REVIEWER does a no-regression review, outcome
    # approved_sprint; TESTER is skipped — the USER was the live validator).
    lifecycle: interactive
    claims_from: [feature_live]
    responsibilities:
      - work_a_single_task_live_with_user_on_leased_stand
      - deploy_to_own_leased_stand_during_session
      - hand_to_review_as_sprint_on_user_ok
      # Scenario-C is cross-scope live work: once PLANNER routed an
      # interactive task to feature_live, LIVE-DEVELOPER owns the live
      # implementation evidence for backend/ui changes on that leased stand.
      - append_implementation_block_for_interactive_backend_or_ui_task
    forbidden_actions:
      - claim_outside_feature_live
      - commit_to_main_directly
      - skip_sprint_review
    event_triggers:
      on_feature_live_claim:
        - create_worktree
        - acquire_lease_via_stand_lease
        - deploy_to_own_leased_stand_during_session
        - interactive_chat_with_user
        - on_user_ok_append_implementation_block
        - release_lease
        - mv_to_feature_review
    stand_profile_selection:
      required_for:
        - feature_live
      default_for:
        feature_live: live_developer
      allowed_used_for:
        - live_ui
        - ui_iteration
      must_select_from: coordination/stand-profiles.yaml
  TECHNICAL-WRITER:
    category: product
    lifecycle: driven
    claims_from: [feature_docs]
    responsibilities:
      - write_documentation
      - file_implementation_block
    forbidden_actions:
      - claim_backend_or_ui_tasks
      - commit_to_main_directly
    event_triggers:
      on_feature_docs_claim:
        - write_docs_in_worktree
        - run_docs_build_if_configured
        - append_implementation_block
        - mv_to_feature_docs_review
  TESTER:
    category: product
    lifecycle: driven
    claims_from: [feature_test]
    responsibilities:
      - probe_feature_on_deployed_stand
      - fill_real_stand_evidence
      - run_gate_check_before_mv
    forbidden_actions:
      - deploy_stand
      - run_ansible_locally
      - fill_fake_stand_evidence
      - claim_outside_feature_test
      # TESTER's only execution surface is SSH probes against the
      # DEPLOYED stand — never a local test run. `uv run`/`uv run
      # --active` from a worktree syncs it into the active fleet environment,
      # dangling its editable .pth on worktree prune →
      # fleet-wide ModuleNotFoundError. Evidence is stand-probe only.
      - run_local_tests
      - uv_run_or_active_against_fleet_venv
    event_triggers:
      on_feature_test_claim:
        - acquire_lease_via_stand_lease
        - wait_for_state_ready
        - probe_via_ssh_using_project_env_host
        - fill_tests_block_with_real_observed_output
        - run_gate_check
        - release_lease_with_result
        - mv_to_feature_review
    stand_profile_selection:
      required_for:
        - plan.stand_required
        - plan.verify_only
      default_for:
        plan.stand_required: feature_test
        plan.verify_only: verify_only
      allowed_used_for:
        - tester_validation
        - deploy_only
        - warmup
        - quick_readiness
      must_select_from: coordination/stand-profiles.yaml
  READER:
    category: product
    lifecycle: driven
    claims_from: [feature_docs_review]
    responsibilities:
      - validate_doc_clarity_as_fresh_user
      - append_reader_review_block
    forbidden_actions:
      - claim_implementer_queues
      - write_code
    event_triggers:
      on_feature_docs_review_claim:
        - read_docs_fresh_no_prior_context
        - append_reader_review_block
        - mv_to_feature_review_on_pass
        - mv_back_to_feature_docs_on_fail_or_partial
        # Audit-only tasks have no WRITER plan to bounce back to —
        # always route them to feature_review, never to feature_docs.
        - route_audit_only_to_feature_review_never_feature_docs
  EXPLORER:
    category: product
    lifecycle: driven
    claims_from: [review_sessions]
    # STAND-ANCHOR (absolute). EXPLORER ALWAYS works ON the stand
    # as a real user, WHATEVER the stand's shape — the access method
    # follows the product (HTTP/browser for a web app, SSH for a host,
    # or wherever the software is deployed); host-destructive/recovery
    # scenarios are EXPLORER's own on the disposable stand. EXPLORER is
    # strictly forbidden from validating off-stand / local substitutes.
    responsibilities:
      - run_review_sessions_on_live_stand
      - operate_on_the_stand_as_real_user_whatever_its_shape
      - file_bugs_to_feature_inbox
    forbidden_actions:
      - implement_code
      - deploy_stand
      - claim_outside_review_sessions
      - validate_off_stand_or_local_substitutes
    event_triggers:
      on_review_sessions_claim:
        - acquire_lease_via_stand_lease
        - run_scenarios_against_deployed_stand
        - file_bugs_as_feature_inbox_tasks
        - append_session_iteration_block
        - release_lease
        - mv_to_archive_when_complete
    stand_profile_selection:
      required_for:
        - review_session
      default_for:
        review_session: explorer
      allowed_used_for:
        - explorer_review
        - full_product_validation
      must_select_from: coordination/stand-profiles.yaml
  USER:
    category: entry
    # USER is the human entry point — interactive by definition.
    lifecycle: interactive
    claims_from: []
    responsibilities:
      - file_user_feedback
      - chat_with_planner
    forbidden_actions:
      - claim_any_queue
      - implement_code
      - write_blocks_directly_to_active_queues
    event_triggers: {}
  MAINTAINER:
    category: system
    # MAINTAINER is self-loop — it wakes itself to drain its inbox +
    # run recovery (no queue-claim, USER-absent operation). It
    # self-paces a recovery tick; it is not a human-paced chat seat.
    lifecycle: self-loop
    # Self-loop cadence (seconds). The recovery watchdog ticks HOURLY —
    # it is not a busy-poll. Pinned here because the agent otherwise
    # defaults to its own ~5-min loop; the cadence must live in canon so
    # it survives fresh sessions and re-bootstraps. A shorter delay is
    # allowed ONLY while actively mid-recovery, then return to this.
    self_loop_wake_seconds: 3600
    claims_from: []
    responsibilities:
      - agent_restart
      - dead_pid_recovery
      - schema_yaml_maintenance
      - cutover_orchestration
      - infra_chat_for_user
      # proactive watchdog sweep each self-loop tick (not reactive-only)
      - proactive_stall_sweep
    forbidden_actions:
      - claim_product_queues
      - triage_user_feedback
      - write_plan_blocks
    event_triggers:
      on_inbox_dead_pid_ask:
        - diagnose_pid_state
        - restart_agent_via_greatminds_restart
      on_inbox_stand_blocked_ask:
        - escalate_or_diagnose_infra
      on_release_cut_ask:
        - run_release_pipeline
      # the self-loop tick itself is a trigger — proactively sweep
      # every tick, before idling, even with an empty inbox.
      on_self_loop_tick:
        - reclaim_stale_stand_lease_past_ttl_with_dead_holder
        - restart_dead_agents_and_coordd_via_greatminds_restart
        - escalate_stalled_or_stuck_chains_to_planner_via_inbox

# ---------------------------------------------------------------------------
# Transitions
#
# Allowed file moves between queues. The `by` field names the only role
# permitted to perform the move. `requires` lists evidence that must be
# present in the task file (front-matter fields or appended blocks) before
# the move is legal.
# ---------------------------------------------------------------------------
transitions:
  # `requires:` is LOAD-BEARING. Each name must have a
  # registered validator in cli/task.py SCHEMA_REQUIRES_VALIDATORS;
  # unknown names raise an error at mv time (no silent documentary
  # keys). The validators run AFTER can_role_move and after the
  # existing require_target_readiness path — they're additive, not a
  # replacement, so adding a new require name today does not need to
  # retire the existing hardcoded check for it.
  #
  # The hardcoded checks in require_target_readiness still handle
  # block-presence + ready_for_* flags for the standard block kinds
  # (triage, plan, implementation, tests, reader_review) — the
  # registry maps those names to no-op validators that just record
  # them as known. The names that get REAL enforcement at the
  # registry layer:
  #   - gate_check_pass_if_stand_required (re-runs the gate-check
  #     evaluation; TESTER cannot bypass by writing pass into the
  #     field manually).
  - from: user_feedback
    to: feature_inbox
    by: ARCHITECT-PLANNER
    requires: [triage_block]
  - from: user_feedback
    to: archive
    by: ARCHITECT-PLANNER
    requires: [triage_block]
  # PLANNER may archive any task in its own intake/draft area when USER
  # decides not to pursue it (split, duplicate, superseded, withdrawn).
  - from: feature_inbox
    to: archive
    by: ARCHITECT-PLANNER
    requires: []
  - from: feature_plan
    to: archive
    by: ARCHITECT-PLANNER
    requires: []
  # review_sessions archive requires terminal-state evidence
  # (at least one session_iteration block, or a blocked block whose
  # reason names "withdrawn"/"abandoned"/"superseded"). Prevents a
  # single role silently wiping an empty or in-progress session.
  - from: review_sessions
    to: archive
    by: ARCHITECT-PLANNER
    requires: [review_session_terminal_block]
  # F: EXPLORER may self-close its own review session when the review
  # campaign is complete (instead of only signalling PLANNER via inbox).
  - from: review_sessions
    to: archive
    by: EXPLORER
    requires: [review_session_terminal_block]
  # Withdrawal path for in-lifecycle work: PLANNER may NOT
  # archive directly from per-scope implementer queues — that silently
  # discards already-written impl/tests/review blocks. The canonical
  # withdraw path is:
  #   1. PLANNER appends a `blocked` block with reason "withdrawn" and
  #      a sentinel `dependencies` that wake-check will never satisfy.
  #   2. PLANNER mv → feature_blocked.
  #   3. ARCHITECT-REVIEWER archives from feature_blocked (the existing
  #      feature_blocked → archive transition by ARCHITECT-REVIEWER).
  # Two roles must agree to terminate in-flight work, never one.
  # REVIEWER may archive blocked tasks that USER decides not to unblock
  # (dependency was retracted, task was superseded, etc).
  # Latest blocked.reason must name a withdrawn-class token; refuses
  # single-role wipe of waiting-but-recoverable work.
  - from: feature_blocked
    to: archive
    by: ARCHITECT-REVIEWER
    requires: [feature_blocked_withdrawn_reason]
  # The lease-based stand model has no terminal-queue transition —
  # release evidence lands on the product task's tests block directly.
  # Entering feature_plan needs a triage block (the plan block is
  # written IN feature_plan, then it routes OUT to a per-scope queue).
  - from: feature_inbox
    to: feature_plan
    by: ARCHITECT-PLANNER
    requires: [triage_block]
  # ARCHITECT-PLANNER routes from feature_plan into per-scope queues.
  # Location = ownership: once in feature_dev/ it is DEVELOPER's; once
  # in feature_ui_dev/ it is UI-DEVELOPER's; once in feature_docs/ it
  # is TECHNICAL-WRITER's. No claim_filter, no content lookup.
  - from: feature_plan
    to: feature_dev
    by: ARCHITECT-PLANNER
    requires: [plan_block, scope_backend]
  - from: feature_plan
    to: feature_ui_dev
    by: ARCHITECT-PLANNER
    requires: [plan_block, scope_ui]
  - from: feature_plan
    to: feature_docs
    by: ARCHITECT-PLANNER
    requires: [plan_block, scope_docs]
  # Independent READER audit: a `kind: docs` task marked audit-only
  # (plan.audit_only: true) is routed straight to READER's queue, with
  # NO prior TECHNICAL-WRITER write. READER audits reality-vs-docs and
  # records findings in a reader_review block; the audit then flows the
  # normal feature_docs_review exits (→ feature_review, or back so
  # PLANNER spawns a WRITER fix task from the findings).
  - from: feature_plan
    to: feature_docs_review
    by: ARCHITECT-PLANNER
    requires: [plan_block, scope_docs, plan.audit_only]
  # Interactive single-task work: a plan marked plan.interactive: true is
  # routed to LIVE-DEVELOPER's queue regardless of scope (backend or ui).
  - from: feature_plan
    to: feature_live
    by: ARCHITECT-PLANNER
    requires: [plan_block, plan.interactive]
  # No-code stand/playbook verification: a plan marked plan.verify_only:
  # true has NO implementer step — PLANNER routes it straight to TESTER,
  # who leases a stand, runs the playbook/probe, and records real stand
  # evidence on a tests block, then advances via the normal feature_test
  # → feature_review → verified path. Mirrors the audit_only feature_docs
  # _review path (skip the implementer, go straight to the verifier).
  - from: feature_plan
    to: feature_test
    by: ARCHITECT-PLANNER
    requires: [plan_block, plan.verify_only]
  - from: feature_dev
    to: feature_test
    by: DEVELOPER
    requires: [implementation_block]
  - from: feature_ui_dev
    to: feature_test
    by: UI-DEVELOPER
    requires: [implementation_block]
  - from: feature_docs
    to: feature_docs_review
    by: TECHNICAL-WRITER
    requires: [implementation_block]
  # LIVE-DEVELOPER hands the USER-approved sprint task to review (no
  # TESTER step). The verified gate accepts review outcome approved_sprint
  # and the sprint review satisfies gate_check_pass_if_stand_required.
  - from: feature_live
    to: feature_review
    by: LIVE-DEVELOPER
    requires: [implementation_block]
  - from: feature_test
    to: feature_review
    by: TESTER
    requires: [tests_block, gate_check_pass_if_stand_required]
  - from: feature_test
    to: feature_dev
    by: TESTER
    requires: [tests_block_fail_or_partial, scope_backend]
  - from: feature_test
    to: feature_ui_dev
    by: TESTER
    requires: [tests_block_fail_or_partial, scope_ui]
  - from: feature_docs_review
    to: feature_review
    by: READER
    requires: [reader_block_pass]
  - from: feature_docs_review
    to: feature_docs
    by: READER
    requires: [reader_block_fail_or_partial]
  - from: feature_review
    to: verified
    by: ARCHITECT-REVIEWER
    requires: [review_block_approved, gate_check_pass_if_stand_required]
  - from: feature_review
    to: feature_dev
    by: ARCHITECT-REVIEWER
    requires: [review_block_changes_requested, scope_backend]
  - from: feature_review
    to: feature_ui_dev
    by: ARCHITECT-REVIEWER
    requires: [review_block_changes_requested, scope_ui]
  - from: feature_review
    to: feature_docs
    by: ARCHITECT-REVIEWER
    requires: [review_block_changes_requested, scope_docs]
  - from: any_active_queue
    to: feature_blocked
    by: current_owner
    requires: [blocked_block_with_dependencies_and_resume_to]
  # User-requested cancellation / withdrawal path. PLANNER may park
  # in-lifecycle work only with a withdrawn-class blocked.reason; REVIEWER
  # remains the only role that can archive it from feature_blocked.
  - from: any_active_queue
    to: feature_blocked
    by: ARCHITECT-PLANNER
    requires: [blocked_block_withdrawn_reason_and_resume_to]
  - from: feature_blocked
    to: any_resume_to_queue
    by: ARCHITECT-REVIEWER
    requires: [all_dependencies_exist_per_wake_check]
  # Stand state transitions happen on the singleton stand resource via
  # the lease API (greatminds stand lease/ready/release/down/up) — see
  # schema.stand.resource.transitions, not this product-task table.
  # Rollback paths from verified/. Two destinations:
  #   - archive       — "withdraw" (the verified work has been reverted
  #                     at code level; FSM record now matches reality)
  #   - feature_review — "revisit" (REVIEWER decides verified work
  #                     needs further amendment; bounce back for fix)
  # Both gated on a rollback block with non-empty reason — operators
  # must record WHY before the FSM exits verified.
  - from: verified
    to: archive
    by: ARCHITECT-REVIEWER
    requires: [rollback_block_with_reason]
  - from: verified
    to: feature_review
    by: ARCHITECT-REVIEWER
    requires: [rollback_block_with_reason]

# ---------------------------------------------------------------------------
# Task front-matter requirements
#
# Required fields when a task enters a given state (queue). Schema validators
# may read this to lint task files. Inline `*_TEMPLATE.md` snippets in
# templates/coordination/<queue>/_TEMPLATE.md show concrete YAML shape.
# ---------------------------------------------------------------------------
task_kinds:
  product:
    required_initial_fields:
      - id
      - stream            # product
      - kind              # feature | bugfix | docs | ops | research
      - scope             # backend | ui | docs | stand | research
      - reporter
      - opened_at
      - priority
      - title
    plan_block_required_fields:
      - written_by
      - written_at
      - base_commit
      - assignee_role
      - stand_required    # true | false
      - stand_reason
      - plan_kind         # full | bugfix   (new in this refactor)
      - mode              # A | B | C       (new in this refactor)
      - ready_for_implementation
    implementation_block_required_fields:
      - closed_by
      - closed_at
      - base_commit
      - files
      - ready_for_test
    tests_block_required_fields:
      - closed_by
      - closed_at
      - base_commit
      - test_files
      - test_command
      - test_result
      - stand_evidence
      - gate_check_result  # new: pass | fail | missing | n/a
      - gate_check_at      # new: ISO timestamp of greatminds gate-check run
      - gate_check_commit  # new: commit checked against stand evidence
      - ready_for_review
    reader_block_required_fields:
      - reviewed_by
      - reviewed_at
      - outcome
      - docs_checked
      - stand_checked
      - command_or_flow
      - ready_for_architect
    review_block_required_fields:
      - reviewed_by
      - reviewed_at
      - outcome
      - commit
    blocked_block_required_fields:
      - blocked_by
      - blocked_at
      - reason
      - dependencies      # list of "<queue>/<task-id>.md"
      - resume_to         # queue name from this schema
  # There is no stand task-kind: the singleton stand resource is
  # lease-based, not a per-request task file — see schema.stand.resource.
  review_session:
    required_initial_fields:
      - id
      - stream            # review_session
      - opened_by
      - opened_at
      - target_functionality
      - scenarios         # list of exploration scenarios
      - stand_target      # host/profile/commit expected
      - mode              # B (scenario B)

# ---------------------------------------------------------------------------
# Scenarios
#
# Three top-level modes of operation. Each task carries `plan.mode: A|B|C`.
# Scenarios are described in prose in COORDINATE.md §6; here only the
# machine-readable mapping to roles and queues.
# ---------------------------------------------------------------------------
scenarios:
  A:
    label: feature_or_refactor
    active_roles: [ARCHITECT-PLANNER, ARCHITECT-REVIEWER, DEVELOPER, UI-DEVELOPER, TECHNICAL-WRITER, TESTER, READER]
    stand_profile: full-deploy
  B:
    label: intensive_review
    active_roles: [ARCHITECT-PLANNER, ARCHITECT-REVIEWER, EXPLORER, DEVELOPER, UI-DEVELOPER, TESTER]
    stand_profile: full-deploy
  C:
    label: ui_rapid_iteration
    active_roles: [LIVE-DEVELOPER]
    stand_profile: vite-dev

# ---------------------------------------------------------------------------
# Runtime access
#
# Hard rule for EVERY role (incl. ARCHITECT-PLANNER): mutation and queue
# inspection for .greatminds/ runtime state is ONLY via the greatminds CLI;
# raw ls/cat/grep/sed/edit on runtime queues/inbox/stand state is forbidden.
# Why: a raw relative path breaks from a per-task worktree cwd; the CLI
# resolves the project root regardless of cwd;
# and a direct task-yaml edit bypasses FSM validation. Every agent
# reads this rule from canon at tick start (machine-readable, no prose).
# ---------------------------------------------------------------------------
runtime_access:
  cli_only: true
  rule: runtime_access_via_greatminds_cli_only
  forbidden:
    - raw_ls_cat_grep_sed_edit_on_runtime_state
  surfaces:
    - greatminds inbox list|show|ack|send
    - greatminds task list|show|mv|append-block
    - greatminds stand status

# ---------------------------------------------------------------------------
# Heartbeat — in-flight-turn hang detection (driven model)
#
# heartbeat is NOT a periodic liveness signal. Between turns a driven
# pane is idle bash — that is normal, not "stale". heartbeat matters in
# exactly one situation: coordd dispatched a turn to an agent and holds
# that role's run-lock. The agent touches .greatminds/heartbeat.<role>
# as a side effect of every greatminds CLI call, so a progressing turn
# keeps it fresh. If a turn is in-flight (run-lock held) AND the
# heartbeat has not advanced for `hang_threshold_seconds`, the turn is
# hung: coordd escalates to MAINTAINER (it does NOT kill or re-drive —
# MAINTAINER decides). When no turn is in-flight, heartbeat age is
# ignored. Liveness of a persistent process is the dead-pid registry
# scan, not heartbeat.
# ---------------------------------------------------------------------------
heartbeat:
  hang_threshold_seconds: 300   # in-flight turn with no heartbeat progress → escalate MAINTAINER

# ---------------------------------------------------------------------------
# Watchdog thresholds
#
# Read by greatminds watchdog. Times in seconds.
# ---------------------------------------------------------------------------
watchdog:
  intent_orphan_seconds: 300            # 5 min
  task_stale_in_active_queue_seconds: 86400   # 24 h
  task_stale_in_review_queue_seconds: 43200   # 12 h

# ---------------------------------------------------------------------------
# Inbox
#
# Inter-role messaging via per-role inbox/<role>/ mailboxes.
# `allowed_kinds` is the closed set of message kinds the inbox CLI accepts;
# any other value is rejected at send time. Mirrored in COORDINATE.md §10.
# ---------------------------------------------------------------------------
inbox:
  allowed_kinds: [wake, ask, info]
  # Per-role message inbox: coordd and other roles message YOU here
  # (wakes, asks, infos). Read it FIRST every tick. It is NOT a task
  # queue — `greatminds task list inbox` is wrong (no such queue).
  dir: ".greatminds/inbox/${GREATMINDS_ROLE}/"
  read: "greatminds inbox list"          # role from $GREATMINDS_ROLE
  show: "greatminds inbox show <path>"
  ack: "greatminds inbox ack <path>"     # after acting on the message
  check_when: "first each tick (wake text: 'check inbox and continue your tick')"

# ---------------------------------------------------------------------------
# Git permissions
#
# Closed set of roles that may run `git commit` / `git push` in this
# project. Enforced by a pre-commit hook (installed by `greatminds setup`)
# that calls `greatminds check-git-permission commit` and exits non-zero
# when $GREATMINDS_ROLE is not in the allowed list. Forbidden verbs are
# `git` subcommands the project never wants any agent to use (force
# operations, history rewrites).
# ---------------------------------------------------------------------------
git_permissions:
  commit: [ARCHITECT-REVIEWER]
  push: [ARCHITECT-REVIEWER]
  forbidden_verbs:
    - reset
    - restore
    - checkout
    - stash
    - rebase
    - revert
    - branch
    - tag

# ---------------------------------------------------------------------------
# Tests block — required-field validation for stand_required tasks
#
# When plan.stand_required is true, the appended tests block MUST contain a
# stand_evidence mapping with the subfields below. Enforced by
# require_block_cross_state in cli/task.py at append-block time and rechecked
# by gate-check during feature_test → feature_review / feature_review →
# verified transitions.
# ---------------------------------------------------------------------------
# Systemic commit-drift fix. The lease deploy path uses the active lease's
# source worktree / task evidence, not whatever main HEAD happens to be at
# deploy time. gate-check compares tests.stand_evidence.commit with the
# task's implementation/plan commit, and compares worktree_fingerprint when
# both sides carry one.
#
# Pairs with worktree_fingerprint: the committed-state identity covers
# what was deployed, the uncommitted-overlay identity covers the
# worktree. Either passing proves «stand tested what impl describes».
stand:
  deploy_strategy:
    target_commit_resolution: evidence_for_impl_base_commit
    post_stand_main_revert: true
  # Stand resource state machine. Singleton resource per project;
  # FIFO lease queue; token-gated access.
  resource:
    states: [free, preparing, ready, down]
    transitions:
      - from: free
        to: preparing
        by: COORDD
        requires: [active_lease_set]
      - from: preparing
        to: ready
        by: COORDD
        requires: [deploy_smoke_ok]
      - from: preparing
        to: down
        by: COORDD
        requires: [deploy_failure_logged]
      - from: ready
        to: free
        by: lease_holder_or_coordd_auto
      - from: ready
        to: down
        by: COORDD
        requires: [infra_incident_logged]
      - from: down
        to: free
        by: COORDD
        requires: [recovery_logged]
    lease:
      ttl_seconds_default: 14400        # 4h
      ttl_warn_window_seconds: 300      # 5min warn before auto-release
      auto_release_on_no_response: true
      # Worktree-path constraint. Stand deploys MUST come from a
      # per-task isolated branch checkout under .worktrees/<seq>; the
      # main fleet tree would self-modify the running host (the very
      # processes we're deploying). The CLI validates the worktree
      # path at acquire-time so a wrong path never reaches state.yaml
      # and never leaves TESTER's lease orphaned in preparing/.
      worktree_constraint:
        pattern: "{project_dir}/.worktrees/{seq}[-<slug>]"
        description: |
          Resolved worktree path's parent dir must be
          ``<project_dir>/.worktrees`` AND its basename must equal the
          task's seq prefix or start with ``<seq>-`` (the slug variant
          ``git worktree add`` produces by default). The CLI rejects
          any other path — including the project_dir itself — with
          exit_code=2 + a message naming the correct path.
        enforced_by: cli
    queue: fifo
    access_control:
      operations:
        - stand_probe_*: requires holder of active_lease's lease_id
      enforcement_layer: cli
    state_file: .stand/state.yaml       # under .greatminds/
    # Profile names are project-defined in coordination/stand-profiles.yaml.
    # The lease CLI accepts a profile only when the registry contains that
    # profile and the referenced YAML playbook exists.

tests_block_validation:
  stand_evidence:
    # Aligned with what gate_check.extract_lease_evidence_from_tests
    # actually validates: the three free-form prose fields below PLUS
    # ``lease_id`` (from the release flow). If lease_id is absent,
    # gate-check returns ``missing`` at the mv-to-feature_review gate.
    required_subfields:
      # Free-form prose (TESTER writes during the lease cycle).
      - reproduction_steps
      - observed_without_fix
      - observed_with_fix
      # Lease-release evidence (lease_id is the gate's hard
      # requirement; ``result`` and ``commit`` are derived with
      # fallbacks but listed here for the contract surface).
      - lease_id
      - result
      - commit
    required_when: plan.stand_required
  # TESTER must run own functional probes against the
  # prepared stand. Deploy/readiness observed_with_fix is infra-
  # readiness only (UP, version, /health 200). TESTER's
  # functional_probes + tester_observations are TESTER's own
  # behavior verification, distinct from readiness checks.
  required_for_scopes:
    backend:
      - functional_probes
      - stand_evidence.tester_observations
    ui:
      - functional_probes
      - stand_evidence.tester_observations
  exempt_scopes:
    - docs        # READER review covers docs
    - research

# ---------------------------------------------------------------------------
# Filesystem layout
#
# Paths relative to <COORDINATION_DIR>. Used by the unified `greatminds` CLI
# and daemon to locate artifacts.
# ---------------------------------------------------------------------------
layout:
  journal: journal.ndjson
  intent_dir: intent/
  inbox_dir: inbox/
  stand_status: stand.status

# ---------------------------------------------------------------------------
# Event-wake dispatch table
#
# coord.yaml's ``tool:`` field per window selects which wake mechanism
# coordd uses for that role on inbox/queue events. Chat-mode (claude)
# roles are woken via the tmux-send-keys mechanism that injects a
# literal keystroke + Enter into the chat-mode pane, identical to
# operator-typed input.
#
# Mechanisms live as functions in cli/coordd.py
# (sigint_sleeping_descendant, tmux_send_keys_wake). This table maps
# tool → mechanism name; the dispatcher picks the function by name.
# ---------------------------------------------------------------------------
event_wake:
  # Default wake mechanism per tool (token defs: glossary.wake_mechanisms).
  # LIFECYCLE WINS: interactive / self-loop roles always use tmux_send_keys
  # regardless of tool — enforced by coordd._wake_mechanism_for_tool.
  by_tool:
    codex:  sigint_deepest_descendant
    cursor: sigint_deepest_descendant
    claude: tmux_send_keys
    cline: tmux_send_keys
    gemini: tmux_send_keys
    openhands: tmux_send_keys
  tmux_send_keys:
    keys: "check inbox and continue your tick"
    enter: true
    rate_limit_seconds: 5

# ---------------------------------------------------------------------------
# FSM single-source-of-truth tables.
#
# These tables are the DATA the FSM runs on (stream block kinds,
# block-kind roles, queue block kinds, impl role by scope, product
# kinds/scopes/priorities, plan kinds, modes, stand enums, test
# results, gate-check results, review/reader outcomes). cli/task.py
# loads + reads them at module-import.
# Validator FUNCTION bodies stay in code (registered by name in the
# transitions[*].requires lists). Adding a new block_kind / scope /
# enum value becomes a schema-only edit — no code change needed for
# the data parts.
# ---------------------------------------------------------------------------

# Streams = top-level task categories. Each declares which block kinds
# its tasks may carry + which header fields are required.
streams:
  product:
    allowed_block_kinds:
      [triage, plan, implementation, tests, reader_review, review, blocked, rollback]
    intake_queues: [feature_inbox, user_feedback]
  # There is no stand stream: the singleton stand resource is
  # lease-based (schema.stand.resource), not a task stream.
  review_session:
    allowed_block_kinds: [session_iteration, blocked]
    intake_queues: [review_sessions]

# Block kinds. Each declares which roles may author it + per-block
# enum constraints. `blocked` is special-cased in code (current owner
# of the queue is the only allowed author) and intentionally omits
# `authored_by` here.
block_kinds:
  triage:
    authored_by: [ARCHITECT-PLANNER]
  plan:
    authored_by: [ARCHITECT-PLANNER]
  implementation:
    authored_by: [DEVELOPER, UI-DEVELOPER, TECHNICAL-WRITER, LIVE-DEVELOPER]
  tests:
    authored_by: [TESTER]
    allowed_test_results: [pass, fail, partial]
    allowed_gate_check_results: [pass, fail, missing, "n/a"]
  reader_review:
    authored_by: [READER]
    allowed_outcomes: [pass, fail, partial]
  review:
    authored_by: [ARCHITECT-REVIEWER]
    # approved_sprint: the light no-regression approval for a LIVE-DEVELOPER
    # sprint task (USER already validated it live; REVIEWER only checks it
    # broke nothing external). Treated as an approve → verified.
    allowed_outcomes: [approved, approved_sprint, changes_requested]
  stand_result:
    authored_by: [COORDD]
    allowed_results: [ok, partial, fail]
    allowed_statuses: [READY, DEGRADED, DOWN, BLOCKED]
  session_iteration:
    authored_by: [EXPLORER]
  blocked:
    # author = current owner of the queue, resolved at runtime
  # rollback block — REVIEWER's withdraw/revisit marker for a
  # task already in verified/. Must carry non-empty ``reason:``.
  rollback:
    authored_by: [ARCHITECT-REVIEWER]

# Which block kinds may be appended to a task currently sitting in
# each queue. Terminal queues accept nothing (empty list). Mirrors
# the prior cli/task.py:QUEUE_BLOCK_KINDS hardcode.
queue_accepts_blocks:
  feature_inbox:       [triage, blocked]
  feature_plan:        [plan, blocked]
  feature_dev:         [implementation, blocked]
  feature_ui_dev:      [implementation, blocked]
  feature_docs:        [implementation, blocked]
  feature_live:        [implementation, blocked]
  feature_test:        [tests, blocked]
  feature_docs_review: [reader_review, blocked]
  feature_review:      [review, blocked]
  feature_blocked:     [blocked]
  user_feedback:       [triage, blocked]
  review_sessions:     [session_iteration, blocked]
  verified:            [rollback]  # rollback only; terminal otherwise
  archive:             []

# Implementation-block scope → which role's queue feeds it. Used by
# intake-rejection + the role-of-implementer dispatch.
assignee_role_by_scope:
  backend: DEVELOPER
  ui:      UI-DEVELOPER
  docs:    TECHNICAL-WRITER

# Product-stream enums for validate_header + validate_block.
product_enums:
  kinds:      [feature, bugfix, docs, ops, research, release]
  scopes:     [backend, ui, docs, stand, research, release]
  priorities: [low, normal, high]
  plan_kinds: [full, bugfix]
  modes:      [A, B, C]

# ---------------------------------------------------------------------------
# Per-task git worktree policy.
#
# Each task gets its own working tree under <project_dir>/<base_path>/
# <task-id>/ on branch <branch_prefix><task-id>. Implementer agents
# cd into their worktree at claim time. Working-tree contamination
# becomes physically impossible across tasks; no locks needed.
#
# Lifecycle is wired into `greatminds task mv` transitions:
#   * mv → feature_dev / feature_ui_dev / feature_docs  → worktree create
#   * mv → verified by REVIEWER                          → worktree merge
#   * mv → archive                                       → worktree remove
#
# All operator-facing operations are exposed by `greatminds worktree
# {create|remove|merge|list|prune}`.
# ---------------------------------------------------------------------------
worktrees:
  base_path: .worktrees                    # relative to project_dir; gitignored
  branch_prefix: task/                     # task ids become task/<id> branches
  default_branch: main                     # branch task worktrees base off + merge back into; set to run coordination on another branch
  merge_strategy: --no-ff                  # explicit per-task boundary in history
  cleanup_on_archive: true
  cleanup_on_verified: true                # after successful merge
  conflict_handback_to: feature_dev        # where REVIEWER hands back on conflict
  required_for_task_kinds: [feature, bugfix, ops]

# USER-curated marketplace plugin lists for `greatminds setup`.
# Verified 2026-05-25 against anthropics/claude-plugins-official.
# Codex side deferred — operator hasn't curated yet, empty list per
# role logs deferral and continues.
plugins:
  claude_marketplace:
    ARCHITECT-PLANNER:  [sourcegraph, sentry, huggingface-skills]
    ARCHITECT-REVIEWER: [sourcegraph, sentry]
    DEVELOPER:          [postman]
    UI-DEVELOPER:       [playwright, chrome-devtools-mcp, postman]
    TESTER:             [playwright, sentry, postman, codspeed]
    READER:             []
    MAINTAINER:         []
  codex_marketplace:
    TECHNICAL-WRITER: []
    EXPLORER:         []

# Agent-uttered visual markers.
#
# After each successful greatminds task mv / append-block / inbox
# send, the agent ends its turn-reply with one markdown line per
# the schema-defined template. Unicode colored emoji + markdown
# bold + inline code = TUI (claude / codex / tmux) renders this
# natively — no ANSI escapes (which most TUIs strip). Operator
# scrolling through any agent pane scans by the emoji color and
# spots major lifecycle events without parsing tool-output blocks.
#
# Detection rules (which event for which CLI verb):
#   - task mv → implementer queue from non-claim queue       → claimed
#   - task mv → feature_test / feature_docs_review from
#     an implementer queue                                   → finished
#   - task mv → verified                                     → accepted
#   - task append-block tests --field test_result=fail|partial
#     OR review --field outcome=changes_requested
#     OR mv back to implementer queue from review queue      → rejected
#   - inbox send                                             → message_sent
#
# Templates use Python str.format placeholders. Available context:
# {emoji}, {task_id}, {from_queue}, {to_queue}, {from_role},
# {to_role}, {kind}, {task_ref}, {reason}. Agents pick the right
# placeholders for each event.
visual_events:
  claimed:
    emoji: "🔵"
    template: "{emoji} **CLAIMED** `{task_id}` ({from_queue}  {to_queue})"
  finished:
    emoji: "🟢"
    template: "{emoji} **FINISHED** `{task_id}`  `{to_queue}`"
  accepted:
    emoji: "🟩"
    template: "{emoji} **ACCEPTED** `{task_id}`  `verified`"
  rejected:
    emoji: "🔴"
    template: "{emoji} **REJECTED** `{task_id}`  {from_role} ({reason})"
  message_sent:
    emoji: "🟣"
    template: "{emoji} **SENT** {kind}  {to_role} {task_ref}"

# Claude Code auto-mode permissions for claude-host roles.
#
# /loop REVIEWER (and any other claude-host role that needs to run
# destructive git ops) cannot do `git commit/tag/push/merge` without
# an explicit allow rule — Claude Code's auto-mode classifier blocks
# destructive Bash by default, and a /loop session has no operator to
# answer an AskUserQuestion prompt. `greatminds setup` writes/extends
# `<project>/.claude/settings.local.json` with these rules per the
# structured-config-first directive. Operator may extend per-project
# by editing settings.local.json directly; setup merges via union,
# never overwriting existing operator entries.
# coordd periodic PyPI version check. Every
# `check_interval_seconds`, coordd compares the installed greatminds
# version against PyPI's latest; if newer, it sends ONE info-message
# to `notify_target`. Notify-only — MAINTAINER decides when to
# actually run `greatminds update`. coordd tracks the last-notified
# version in process memory so a still-un-acted-upon newer release
# doesn't spam every poll.
auto_update:
  check_interval_seconds: 14400   # 4h
  notify_target: MAINTAINER
  mode: notify_only               # future: auto_execute
  source: pypi                    # future: git_tag

# Gate `greatminds report-upstream` to MAINTAINER only. Other
# roles file bug-suspects via `greatminds inbox send MAINTAINER
# --kind ask`. CLI refuses for non-allowed agent roles; operator-
# mode (no GREATMINDS_ROLE) bypasses the gate.
report_upstream:
  permissions:
    invoke: [MAINTAINER]
  triage_inbox_target: MAINTAINER
  upstream_repo_required: true

# Canonical list of system variables greatminds reads out of
# the box. ``setup`` generates ``.greatminds/PROJECT.env`` from this
# section, pre-populating each var with its description / acquire
# instructions. Users add their own KEY=value to PROJECT.env after
# setup; only the entries listed here come from canon.
project_env:
  system_vars:
    GREATMINDS_UPSTREAM_TOKEN:
      required: false
      description: |
        GitHub PAT for `greatminds report-upstream --mode api-token`.
        Skip if using --mode gh (gh auth login) instead.
      usage_locations: [src/greatminds/cli/report_upstream.py]
      acquire_instructions: |
        github.com → Settings → Developer settings → Personal access
        tokens. Scope: issues:write on the upstream repo
        (default veryviolet/greatminds).

claude_settings:
  permissions:
    allow:
      # Broad default: allow ALL Bash for every project. Driven /
      # headless agents (TESTER stand probes, coordd deploys —
      # sudo/docker/compose/rsync, MAINTAINER recovery) repeatedly
      # stalled because a needed command wasn't in a narrow allow-list
      # and a headless turn can't answer a permission prompt. These are
      # the operator's own dev fleets; agents must never block on Bash.
      # The specific git/ssh/rsync rules below are redundant under this
      # but kept as documentation of the original intent.
      - "Bash"
      - "Bash(git add:*)"
      - "Bash(git commit:*)"
      - "Bash(git tag:*)"
      - "Bash(git push:*)"
      - "Bash(git merge:*)"
      - "Bash(git worktree:*)"
      - "Bash(git checkout:*)"
      - "Bash(git branch:*)"
      # REVIEWER's rollback path (verified → revert) needs git
      # revert; TESTER's stand probe needs ssh/scp; coordd's
      # deploy uses rsync. Without these allow rules they had to be
      # typed manually with the `!` prefix on every invocation.
      - "Bash(git revert:*)"
      - "Bash(ssh:*)"
      - "Bash(scp:*)"
      - "Bash(rsync:*)"
  # Classifier auto-mode ceiling. Without explicit auto_mode
  # entries the classifier blocks `git push origin main` even when
  # permissions.allow contains `Bash(git push:*)`. These rules raise
  # the ceiling so REVIEWER's automated cut-and-push pipeline works
  # without manual `!` prefix on each push.
  auto_mode:
    allow:
      - "$defaults"
      # NOTE: deliberately NOT a bare "Bash" here — auto_mode is the
      # classifier's OWN ceiling for USER-present interactive turns, and a
      # wildcard there is an auto-mode bypass (rightly refused). Driven /
      # headless agents are gated by permissions.allow (which DOES allow
      # all Bash), not by this ceiling, so the deploy/probe unblocking
      # works without widening the interactive classifier gate.
      - "Bash(git push origin main:*)"
      - "Bash(git push origin main --follow-tags:*)"
      - "Bash(git push --follow-tags:*)"
      # REVIEWER's canonical terminal transition is not raw git: it is
      # `greatminds task mv <id> verified`, which performs the sanctioned
      # worktree merge. Driven Claude REVIEWER sessions must be able to run
      # that FSM command, and the related worktree lifecycle commands, under
      # auto-mode.
      - "Bash(greatminds task mv:*)"
      - "Bash(greatminds worktree:*)"
      - "Bash(git revert:*)"
      # ssh/scp/rsync also need to live in auto_mode.allow, not just
      # permissions.allow — the classifier's auto-mode ceiling is a
      # SEPARATE gate. Without the parallel entries a stand probe from
      # a --permission-mode auto pane is still challenged for USER
      # session auth on every invocation.
      - "Bash(ssh:*)"
      - "Bash(scp:*)"
      - "Bash(rsync:*)"
      # MAINTAINER is self-loop (USER-absent) and runs recovery —
      # agent restart, daemon control, dead-pid
      # SIGTERM. The classifier's auto-mode ceiling blocked these
      # without explicit entries, so MAINTAINER's autonomous
      # recovery stalled waiting for a USER who isn't there. These
      # patterns unblock the recovery path.
      - "Bash(greatminds restart:*)"
      - "Bash(greatminds daemon:*)"
      - "Bash(greatminds start-agent:*)"
      - "Bash(kill:*)"
      - "Bash(systemctl --user:*)"

stand_profile_registry:
  path: coordination/stand-profiles.yaml
  environment_values:
    stand: "Disposable or staging-like live stand."
    production: "Production environment; requires explicit approval policy."
  used_for_values:
    deploy_only: "Deploy/readiness check without product behavior validation."
    warmup: "Prepare prerequisites before a validation lease."
    quick_readiness: "Cheap liveness/reachability check."
    tester_validation: "TESTER product validation for stand-required tasks."
    explorer_review: "EXPLORER scenario-B live product review."
    live_ui: "LIVE-DEVELOPER scenario-C UI iteration."
    ui_iteration: "UI-focused live iteration."
    reviewer_validation: "ARCHITECT-REVIEWER final stand verification."
    full_product_validation: "End-to-end deployed product validation."
    production_deploy: "Production deployment task."
    production_post_deploy_review: "Post-deploy production verification."
  default_for_values:
    verify_only: "No-code stand/playbook verification task."
    feature_test: "TESTER validation after implementation."
    explorer: "EXPLORER review-session lease."
    live_developer: "LIVE-DEVELOPER interactive lease."
    ui_iteration: "UI-focused live iteration."
    reviewer: "ARCHITECT-REVIEWER final verification lease."
    production_deploy: "Deployment to production."
    production_review: "Review that production deployment is complete and healthy."

# Stand profile files. coordd reads the YAML file referenced by
# ``coordination/stand-profiles.yaml`` for ``lease.profile`` and executes it as
# part of the lease deploy.
#
#   * ``yaml`` — a subset of ansible-playbook syntax, run by coordd's
#     deterministic deploy engine.
#
stand_profile:
  directory: coordination/stand-profiles
  formats: [yaml]
  lookup: "coordination/stand-profiles.yaml profiles.<lease.profile>.file"
  yaml_dialect: ansible_playbook
  yaml_required_fields: [name, hosts, tasks]
  yaml_optional_fields: [vars, handlers, gather_facts]
  # Opt-out flag carried on lease metadata so the deploy path runs only the
  # "prerequisites" subset of the profile (used by warmup leases
  # that prepare the stand for the real probe).
  deploy_prerequisites_only_flag: deploy_prerequisites_only

# Stand lifecycle notification policy. coordd files inbox-info
# automatically on these events so PLANNER (or another role) does not need to
# poll state.yaml. Keep the map small — only events PLANNER must react to.
stand_keeper:
  notifications:
    on_down: ARCHITECT-PLANNER