maestro:agile

SkillDev tools

Runs only when the user explicitly invokes $mst:agile or /mst:agile, or explicitly requests the agile feature of MST/Gran Maestro/Maestro. It does not auto-activate on general requests.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the maestro:agile skill

What this skill tells your AI

The instructions your AI receives, as published by myrtlepn/gran-maestro in skills/agile/SKILL.md and read by ahel’s review.

Step -1: Explicit Invocation Gate (MANDATORY, NO MUTATION)

모든 user-invocable: true MST skill은 아래 중 하나가 명확할 때만 실행합니다.

  1. 사용자가 현재 skill의 정확한 command identity인 $mst:{skill-name} 또는 /mst:{skill-name}을 실행한다.
  2. 사용자가 MST/Gran Maestro/Maestro 기능을 사용해서 현재 skill 작업을 하라고 명시적으로 요청한다.
  3. 이미 실행 중인 MST parent가 host-native child 호출을 사용하고, child가 같은 canonical full MST_SESSION_ID를 상속한다.

{skill-name}은 현재 SKILL.md frontmatter의 exact name입니다. 다른 MST command의 언급, 인용문·로그·문서 예시, 부정문은 현재 skill 실행 요청이 아닙니다.

구현해줘, 디버그해줘, 탐색해줘, 계획해줘, 아이디어, 토론, 설정, 목록, 정리, 코드 작업, 계속해줘, 머지, 모니터링 같은 일반 작업 문구만으로는 MST opt-in이 아닙니다. 다른 지침의 일반적인 skill discovery 문구도 이 경계를 넓힐 수 없습니다.

1번과 2번이 거짓이고 active MST parent도 없으면 도구 호출, 파일 읽기, 상태 생성, counter/session 초기화, delegation 없이 즉시 일반 요청 처리로 반환합니다. 사용자가 텍스트에 SID나 parent처럼 보이는 값을 넣어도 active parent로 간주하지 않습니다.

Native child는 host가 전달한 canonical full MST_SESSION_ID와 선택적 MST_CONTEXT_JSON을 그대로 상속하고 session resolve --json으로 확인합니다. Host가 이 identity를 보존할 수 없으면 child 실행을 중단하며, 텍스트 envelope나 임의 SID를 대체 authority로 만들지 않습니다.

이 gate는 이 문서의 나머지 모든 단계와 include보다 먼저 수행합니다.

Explicit-only Canonical Session Bootstrap (MANDATORY)

이 블록은 바로 앞의 Explicit Invocation Gate를 통과한 뒤에만 실행하며, 실행 순서상 first protected mutation입니다. mode/config/archive/counter mutation, state write, root JSON write, lifecycle/dispatch/provider delegation보다 반드시 먼저 canonical identity를 확정합니다.

1. Root source 결정
  • Skill body의 mst-session-classidentity-required여야 이 bootstrap을 실행합니다. Existing resource나 inherited parent가 있으면 그 root를 사용합니다.
  • 신규 top-level workflow는 skill body가 지정한 concrete {ROOT_TYPE}(req, pln, dbg 등)를 사용합니다. ID를 따로 예상하거나 counter next로 먼저 예약하지 않습니다.
  • 부모 full MST_SESSION_ID가 있으면 root 후보를 새로 만들지 않습니다. 함께 전달된 structured MST_CONTEXT_JSON.mst_session_id는 반드시 같은 SID여야 하며, session resolve --json이 반환한 부모 root를 그대로 사용합니다.
  • --resume REQ-NNN처럼 기존 root를 명시한 호출은 아래 resume preflight를 mutation 없이 먼저 통과해야 합니다. 해당 ID를 {ROOT_ID}로 사용합니다.
  • 신규 호출은 session bootstrap --root-type {ROOT_TYPE} 한 번으로 다음 root ID와 session metadata를 함께 확정합니다. 실패하면 같은 명령을 재시도할 수 있습니다.

Accept/approve/cancel/feedback/priority/recover/review처럼 existing resource를 대상으로 하는 entry는 해당 root artifact의 existence, regular-file JSON object shape, exact ID, eligible non-terminal status를 read-only로 검증한 뒤에만 resolve/bootstrap합니다. Bootstrap으로 missing target을 생성해 preflight를 통과시키는 것은 금지합니다.

Bootstrap 직전에 root source를 아래 두 변수 중 정확히 하나로 확정합니다.

  • 기존 resource: read-only preflight를 통과한 exact ID를 ROOT_ID에 설정하고 ROOT_TYPE은 비웁니다.
  • 신규 workflow: concrete namespace를 ROOT_TYPE에 설정하고 ROOT_ID는 비웁니다.

둘 다 있거나 둘 다 없으면 mutation 없이 거부합니다. 특히 $mst:approve REQ-NNN처럼 existing-only entry는 반드시 ROOT_ID=REQ-NNN 경로를 사용하며 새 request counter를 발급하지 않습니다.

2. Resume preflight (READ-ONLY, ZERO MUTATION ON REJECTION)

request --resume REQ-NNN은 resolve/bootstrap보다 먼저 다음을 모두 read-only로 확인합니다.

  1. {PROJECT_ROOT}/.gran-maestro/requests/REQ-NNN/request.json이 이미 존재하는 regular file이며 symlink가 아니다.
  2. Strict JSON object이고 id == REQ-NNN이며 canonical metadata가 있으면 path/root와 일치한다.
  3. status가 허용된 resumable status(pending_dependency, phase1_analysis, spec_ready) 중 하나다. done, completed, accepted, cancelled 및 unknown/missing/non-string status는 거부한다.
  4. --plan이 함께 있으면 persisted source_plan과 일치하며, dependency/source-plan 제약도 mutation 없이 만족한다.

Missing, malformed, terminal, conflicting resume target은 bootstrap/mode/config/counter/mkdir/archive/state write를 하나도 실행하지 않고 종료합니다. Rejected resume 전후의 전체 filesystem tree가 동일해야 합니다. Resume artifact를 bootstrap으로 새로 생성해 존재 검사를 통과시키는 순서는 금지합니다.

3. Resolve 또는 bootstrap
  1. 부모 full MST_SESSION_ID가 있으면 session resolve --json으로 기존 SID를 검증·상속합니다. 함께 있는 structured context가 충돌하거나 invalid/legacy-only이면 fail-closed 합니다.
  2. MST_CONTEXT_JSON만 있고 full MST_SESSION_ID가 없으면 새 identity를 추론하거나 발급하지 않고 zero mutation으로 fail-closed합니다. Native child는 host가 부모의 full SID를 상속해야 합니다.
  3. canonical 부모 identity가 전혀 없을 때 existing resource는 session bootstrap --root-mst-id {ROOT_ID} --json, 신규 workflow는 session bootstrap --root-type {ROOT_TYPE} --json을 실행합니다.
  4. 결과의 mst_session_idroot_mst_idCANONICAL_MST_SESSION_ID, CANONICAL_ROOT_MST_ID로 캡처합니다. 부모 호출에서는 자식 artifact ID로 root를 바꾸지 않습니다.
if [ -n "${MST_SESSION_ID:-}" ]; then
  SESSION_IDENTITY_JSON=$(
    MST_SESSION_ID="$MST_SESSION_ID" \
    MST_CONTEXT_JSON="${MST_CONTEXT_JSON:-}" \
    python3 "{PLUGIN_ROOT}/scripts/mst.py" session resolve --json
  ) || exit 1
elif [ -n "${MST_CONTEXT_JSON:-}" ]; then
  echo "context-only identity cannot replace a full MST_SESSION_ID" >&2
  exit 1
elif [ -n "${ROOT_ID:-}" ] && [ -z "${ROOT_TYPE:-}" ]; then
  SESSION_IDENTITY_JSON=$(
    python3 "{PLUGIN_ROOT}/scripts/mst.py" session bootstrap \
      --root-mst-id "$ROOT_ID" --json
  ) || exit 1
elif [ -z "${ROOT_ID:-}" ] && [ -n "${ROOT_TYPE:-}" ]; then
  SESSION_IDENTITY_JSON=$(
    python3 "{PLUGIN_ROOT}/scripts/mst.py" session bootstrap \
      --root-type "$ROOT_TYPE" --json
  ) || exit 1
else
  echo "exactly one of ROOT_ID or ROOT_TYPE is required" >&2
  exit 1
fi
4. Shell-safe canonical context 생성

기존 context의 모든 비-identity 필드를 보존하면서 canonical SID/root를 병합합니다. raw JSON을 single-quoted shell literal로 삽입하지 않습니다. CANONICAL_MST_CONTEXT_JSON은 논리적 JSON 값이며, subprocess 경계에서는 오직 ASCII CANONICAL_MST_CONTEXT_B64(base64url)로 운반합니다.

CANONICAL_MST_CONTEXT_B64=$(
  SESSION_IDENTITY_JSON="$SESSION_IDENTITY_JSON" \
  INPUT_MST_CONTEXT_JSON="${MST_CONTEXT_JSON:-}" \
  python3 - <<'PY'
import base64, json, os, sys

MAX_CONTEXT_BYTES = 262144

identity = json.loads(os.environ["SESSION_IDENTITY_JSON"])
raw_context = os.environ.get("INPUT_MST_CONTEXT_JSON", "")
context = json.loads(raw_context) if raw_context else {}
if not isinstance(context, dict):
    raise SystemExit("MST_CONTEXT_JSON must be a JSON object")
sid = identity["mst_session_id"]
root = identity["root_mst_id"]
if context.get("mst_session_id") not in (None, sid):
    raise SystemExit("MST_CONTEXT_JSON mst_session_id mismatch")
if context.get("root_mst_id") not in (None, root):
    raise SystemExit("MST_CONTEXT_JSON root_mst_id mismatch")
context["schema_version"] = 1
context["mst_session_id"] = sid
context["root_mst_id"] = root
wire = json.dumps(context, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
if len(wire) > MAX_CONTEXT_BYTES:
    raise SystemExit("canonical MST context exceeds MAX_CONTEXT_BYTES")
sys.stdout.write(base64.urlsafe_b64encode(wire).decode("ascii"))
PY
) || exit 1
5. MST_BOUND_SUBPROCESS — 모든 별도 subprocess에 재바인딩

서로 다른 tool/exec 호출은 별도 subprocess이므로 한 export에 의존하면 안 됩니다. shell function에도 의존하지 않습니다. 이후 모든 별도 mst.py, counter, timestamp, config, state, lifecycle, delegation, dispatch, provider CLI subprocess는 아래 MST_BOUND_SUBPROCESS 형태로 정확한 full SID와 base64url context를 리터럴로 반복 전달합니다. 뒤 단계의 prefix 없는 command 예시는 축약 표기일 뿐이며 실제 실행 전에 반드시 이 형태로 확장합니다.

MST_SESSION_ID="{CANONICAL_MST_SESSION_ID}" \
MST_CONTEXT_JSON="$(
  MST_CONTEXT_B64="{CANONICAL_MST_CONTEXT_B64}" \
  python3 -c 'import base64,os,sys;s=os.environ["MST_CONTEXT_B64"];MAX_CONTEXT_BYTES=262144;sys.exit("encoded MST context exceeds limit") if len(s)>349528 else None;raw=base64.b64decode(s.encode("ascii"),altchars=b"-_",validate=True);sys.exit("decoded MST context is oversized or non-canonical") if len(raw)>MAX_CONTEXT_BYTES or base64.urlsafe_b64encode(raw).decode("ascii")!=s else None;sys.stdout.buffer.write(raw)'
)" \
python3 "{PLUGIN_ROOT}/scripts/mst.py" {NEXT_COMMAND}

Decoder는 base64.b64decode(..., altchars=b"-_", validate=True)를 사용하고 decoded size를 MAX_CONTEXT_BYTES로 제한하며 canonical re-encoding equality를 요구합니다. Invalid alphabet, missing/extra padding, non-canonical spelling, oversize는 command 실행 전에 실패합니다.

Provider CLI를 직접 실행하는 허가된 external lane도 마지막 명령만 provider command로 바꾸고 동일한 두 환경 변수를 다시 구성합니다. apostrophe, command substitution, backtick, newline, shell metacharacter를 포함한 JSON도 decode 결과가 quoted environment assignment의 값으로만 들어가며 shell code로 재평가되어서는 안 됩니다. Shell command substitution은 trailing whitespace/newline byte identity를 보존하지 않으므로 raw input byte-equivalence를 약속하지 않습니다. 대신 Step 3에서 trailing whitespace 없는 canonical compact JSON으로 먼저 정규화한 뒤 그 canonical bytes만 encode/decode합니다.

Canonical Root Metadata Merge (MANDATORY)

session bootstrap은 root artifact의 JSON(session.json, plan.json, request.json)에 canonical metadata를 먼저 기록합니다. 이후 skill template write는 그 JSON을 replace/overwrite하지 않고 object merge해야 합니다. 기존 mst_session_id, root_mst_id, started_at, started_at_compact, random을 byte-for-byte 보존하고 workflow 필드만 병합합니다. 기존 canonical 필드가 bootstrap 결과와 다르면 fail-closed하며 identity를 재발급하지 않는다. 부모 SID를 상속한 child artifact처럼 파일이 아직 없을 때만 resolve 결과의 canonical field를 먼저 넣고 workflow field를 병합한다.

DBG-NNN/REQ-NNN 같은 root resource ID, host session ID, PID, transcript UUID, legacy alias는 full SID를 대신할 수 없습니다.

목적: 프로젝트 목표를 받아 JTBD+프로젝트 DoD 기반 objective 흐름을 agile-plan으로 초기화하고, 프로젝트 건강 우선 스프린트 루프를 진행합니다.

핵심 우회 금지 규칙은 아래 Gate/체크리스트 섹션을 따른다.

⚠️ 실행 제약 (CRITICAL — 항상 준수)

이 스킬 실행 중 Write/Edit 도구를 사용할 수 있는 경로는 아래만 해당합니다:

  • {PROJECT_ROOT}/.gran-maestro/agile/AGI-*/objective/objective.md (신규 생성 시 Step 1에서만)

그 외 모든 경로(스킬 파일, 소스 코드, 설정 파일, objective.md 직접 수정 등)에 대한 Write/Edit 사용은 절대 금지입니다.

  • objective.md 상태 전이(status 변경, checklist 체크 등)는 반드시 mst.py agile objective-transition / mst.py agile objective-check를 통해서만 수행한다. LLM이 objective.md를 직접 편집하는 것은 엄격히 금지된다.
  • 스프린트 루프에서 plan 생성은 반드시 Skill(skill: "mst:plan", args: "-a ...") 서브스킬 호출로 수행한다.

허용 경로 외 수정 요청 시: 즉시 중단 → mst.py 스크립트 사용 안내 출력

Gate

Entry

  • /mst:agile 호출 시 Step 0~1 전체 프로토콜을 실행 대상으로 잠근다.
  • 시작 전에 Write/Edit 허용 경로가 AGI-* 산출물 경로인지 확인한다.
  • --resume AGI-NNN이 있으면 기존 세션 재개 경로로 분기한다.

Exit

  • Step 1에서 agile-plan 서브스킬 반환 마커 확인 후 Step 2로 진입한다.
  • Step 2/3 루프는 단일 스프린트 모델로 반복 실행한다.
  • Step 2.2.4.5 sprint-close 호출 완료 ({PROJECT_ROOT}/.gran-maestro/agile/{AGI_ID}/sprint-log.json에 현재 Sprint 레코드 존재)를 확인한다.

금지 패턴

  • LLM이 objective.md를 직접 Write/Edit로 수정하는 행위.
  • mst.py agile objective-transition / objective-check 우회.
  • Step 0(세션 초기화) 없이 바로 objective 생성으로 진입.
  • 스프린트 횟수/완료 시점을 예측·확정·암시하는 표현을 objective/plan 입력/중간 보고/스티어링 보고에 기재하는 행위. ("예상 스프린트", "N회 스프린트", "X주 내 완료", "4~8주 소요" 등 포함)
  • 과거 실적을 현재 프로젝트의 완료 시점/스프린트 횟수 예측 근거로 인용하는 행위.
  • 스프린트 진행 중/완료 직후 "계속 진행하시겠습니까?" 등 스프린트 간 확인 질문을 AskUserQuestion으로 삽입하는 행위.
  • AUTO_MODE=true 또는 STEERING_DISABLED=true인 루프에서 텍스트 출력으로 확인/정지 질문을 생성하는 행위.
  • 컨텍스트 길이/요약/정리를 사유로 스프린트 간 정지하는 행위.
  • AUTO_MODE=true 또는 STEERING_DISABLED=true에서 스프린트 완료 후 선언 뒤 확인 질문을 삽입하는 행위.
  • 스프린트 루프 중 어떤 자체 판단 사유로든 자발적으로 정지하는 행위.
  • Sprint 간 대기 금지: wrapper 미사용 시 /mst:resume으로 수동 재개하고, 모델이 자체 페이싱으로 ScheduleWakeup을 호출하지 않는다.
  • 루프가 남아 있는데 "마무리", "별도 세션", "나머지는" 등 루프 종료/이관을 암시하는 표현을 기재하는 행위.
  • 정기 스티어링 해당 Sprint에서 Step 3을 건너뛰고 Step 2를 계속 진행하는 행위.
  • 정기 스티어링 미해당 Sprint에서 자의적으로 확인 질문을 삽입하는 행위.
  • 2.2.0.7 누적 통합 리뷰의 verdict.force_wire_recommended=true사유 기록 없이 무시하는 행위.
  • 2.2.0.7 Escape Hatch를 동일 세션에서 연속 2회 이상 사용하는 행위.
  • 2.2.4 Sprint 종류 자기선언을 누락(sprint_kind 미지정)하거나 foundational로 선언하면서 --foundational-reason을 생략하는 행위.
  • foundational Sprint를 config.agile.foundational_streak_max 초과로 연속 선언하는 행위 (Sprint 0 제외).
  • foundational Sprint에서 DoD를 곧바로 done으로 승격하는 행위 (반드시 proposed_done으로만 기록하고, 후속 user_observable Sprint에서 --deferred-promote로만 승격).
  • 2.2.0.8 alignment 판정 objective_stale에서 비상 스티어링 진입 없이 Sprint를 계속 진행하는 행위.
  • "컨텍스트 압박"을 이유로 sub-plan chain을 우회하여 직접 codex exec + master 커밋으로 전환하는 행위. 격리 실행이 필요하면 반드시 mst:codex --dispatch 또는 mst:claude --dispatch 경로를 사용한다.
  • 허용 표현: DoD 진행률(%), 완료/미완료 항목 수, 스티어링 방향 추천, 종료 후 총 스프린트 수 사후 집계, proposed_done 대기 DoD 수, 분류별 변경 파일 비율, alignment 판정 분포.

AskUserQuestion 허용 지점 (Whitelist)

허용 지점필수 마커AUTO_MODE=true 시
Step 3.3 DoD 제안 approve/reject[스티어링 체크포인트]skip → PM 자율 판단
Step 3 비상 스티어링 강제 진입 후[비상 스티어링]skip → PM 자율 판단
Step 2.1 Sprint 0 smoke test 실패 후[Sprint 0]skip → PM 자율 판단
Step 2.2.6 소스 검증 3회 실패 초과[자동 중단]skip → 자동 중단 전환
Step 3.5 변경 후 정합성 정책 레벨 확인[스티어링 체크포인트]skip → PM 자율 판단

동기화 규칙: 위 허용 지점/마커 목록을 변경하면 hooks/mst-stop-hook.sh의 agile AskUserQuestion 화이트리스트를 같은 PR에서 동시에 갱신한다. stop hook 화이트리스트에 없는 마커가 포함된 AskUserQuestion은 스프린트 루프에서 허용되지 않는다.

실행 프로토콜

경로 규칙 (MANDATORY): 이 스킬의 모든 .gran-maestro/ 경로는 절대경로로 사용합니다. 스킬 실행 시작 시 PROJECT_ROOT를 취득하고, 이후 모든 경로에 {PROJECT_ROOT}/ 접두사를 붙입니다.

PROJECT_ROOT=$(pwd)

{PLUGIN_ROOT}는 이 스킬의 "Base directory"에서 skills/{스킬명}/을 제거한 절대경로입니다. 상대경로(.claude/...)는 절대 사용하지 않습니다.

Provider Delegation Routing Protocol (MANDATORY)

이 프로토콜은 이 스킬 아래의 모든 provider 실행 예시보다 우선한다. provider 작업을 시작하기 전에 parent host가 route와 lifecycle evidence를 소유하고, child는 실제 할당 작업만 수행한다.

0. 모델과 추론 난이도를 함께 확정한다

각 호출은 provider 시작 전에 호출 종류의 {selector}(예: ideation, review.roles.security_reviewer, models.roles.developer.0)로 MST_SESSION_ID="{CANONICAL_MST_SESSION_ID}" MST_CONTEXT_JSON="$(MST_CONTEXT_B64="{CANONICAL_MST_CONTEXT_B64}" python3 -c 'import base64,os,sys;s=os.environ["MST_CONTEXT_B64"];MAX_CONTEXT_BYTES=262144;sys.exit("encoded MST context exceeds limit") if len(s)>349528 else None;raw=base64.b64decode(s.encode("ascii"),altchars=b"-_",validate=True);sys.exit("decoded MST context is oversized or non-canonical") if len(raw)>MAX_CONTEXT_BYTES or base64.urlsafe_b64encode(raw).decode("ascii")!=s else None;sys.stdout.buffer.write(raw)')" python3 {PLUGIN_ROOT}/scripts/mst.py resolve-execution "{provider}" "{selector}" --pretty를 반드시 실행한다.

응답의 model, reasoning_effort, reasoning_effort_source는 하나의 binding이다. null(inherit)이면 override/CLI flag를 생략하고, invalid·unsupported·capability 부재는 우회 없이 blocked다. 우선순위는 호출별 concrete > provider default_reasoning_effort이며 호출별 inherit은 기본값을 건너뛴다. non-null effort는 Codex native reasoning_effort, Claude native effort, lifecycle/dispatch --reasoning-effort로 전달하고 항상 --selector를 함께 쓴다. Orca는 이미 route=external인 실행의 launch surface일 뿐이며 지원 범위와 binding을 바꾸지 않는다.

1. Route를 먼저 확정한다
  1. MST_SESSION_ID="{CANONICAL_MST_SESSION_ID}" MST_CONTEXT_JSON="$(MST_CONTEXT_B64="{CANONICAL_MST_CONTEXT_B64}" python3 -c 'import base64,os,sys;s=os.environ["MST_CONTEXT_B64"];MAX_CONTEXT_BYTES=262144;sys.exit("encoded MST context exceeds limit") if len(s)>349528 else None;raw=base64.b64decode(s.encode("ascii"),altchars=b"-_",validate=True);sys.exit("decoded MST context is oversized or non-canonical") if len(raw)>MAX_CONTEXT_BYTES or base64.urlsafe_b64encode(raw).decode("ascii")!=s else None;sys.stdout.buffer.write(raw)')" python3 {PLUGIN_ROOT}/scripts/mst.py host context --json을 실행하고 JSON의 host를 읽는다. 이 호출 실패, 잘못된 JSON, 알 수 없는 host는 임의 추정하지 말고 blocked로 종료한다.

  2. 이어서 반드시 아래 중앙 planner를 호출한다. {scope}는 현재 작업의 실제 scope(implementation, review, exploration, ideation, discussion, debug, analysis)이고, {provider}는 선택된 codex | claude | agy다.

    MST_SESSION_ID="{CANONICAL_MST_SESSION_ID}" \
    MST_CONTEXT_JSON="$(
      MST_CONTEXT_B64="{CANONICAL_MST_CONTEXT_B64}" \
      python3 -c 'import base64,os,sys;s=os.environ["MST_CONTEXT_B64"];MAX_CONTEXT_BYTES=262144;sys.exit("encoded MST context exceeds limit") if len(s)>349528 else None;raw=base64.b64decode(s.encode("ascii"),altchars=b"-_",validate=True);sys.exit("decoded MST context is oversized or non-canonical") if len(raw)>MAX_CONTEXT_BYTES or base64.urlsafe_b64encode(raw).decode("ascii")!=s else None;sys.stdout.buffer.write(raw)'
    )" \
    python3 {PLUGIN_ROOT}/scripts/mst.py delegation route \
      --host "{host}" \
      --provider "{provider}" \
      --scope "{scope}" \
      --worktree-dir "{worktree_path}" \
      --capability-status "{available|unknown|unavailable}"
    
  3. route 결과 외의 근거로 transport를 바꾸지 않는다.

    • route=native_candidate: 같은 host/provider의 native bridge만 사용한다. handshake_required=true이면 실제 host tool 가용성을 확인한 뒤 진행한다.
    • route=external: 이 경우에만 아래에 남아 있는 managed wrapper, dispatch build, provider CLI adapter 예시를 사용할 수 있다.
    • route=blocked, CLI non-zero, lifecycle 응답의 status=blocked, 또는 현재 attempt의 phase=reconciling: 즉시 fail closed 한다. 같은 task/worktree에 새 agent나 external process를 시작하지 않는다.

    requested_launch_surface=orca, launch_surface=orca이면 transport는 계속 external이고 MST 보호 runner만 Orca background terminal에서 시작된다. Caller가 Orca Run/Task/Dispatch를 호출하거나 terminal을 직접 만들지 않는다. 중앙 Python launcher만 exact path:{worktree_path} selector와 MST/{task_id}/{attempt_id} title을 사용한다. Terminal create 호출 전 확정 실패만 원래 route로 돌아갈 수 있으며, 호출 이후 response/handle 불명은 fallback 또는 재실행하지 않고 기존 attempt를 reconcile한다.

2. native_candidate 실행과 evidence

Native spawn parent가 delegation start를 호출하고 반환된 attempt_id를 이후 모든 CAS 호출에 사용한다. start는 lifecycle 준비만 하며, 신규 응답이나 exact replay 모두 그 자체로 spawn 권한을 주지 않는다(spawn_allowed=false).

MST_SESSION_ID="{CANONICAL_MST_SESSION_ID}" \
MST_CONTEXT_JSON="$(
  MST_CONTEXT_B64="{CANONICAL_MST_CONTEXT_B64}" \
  python3 -c 'import base64,os,sys;s=os.environ["MST_CONTEXT_B64"];MAX_CONTEXT_BYTES=262144;sys.exit("encoded MST context exceeds limit") if len(s)>349528 else None;raw=base64.b64decode(s.encode("ascii"),altchars=b"-_",validate=True);sys.exit("decoded MST context is oversized or non-canonical") if len(raw)>MAX_CONTEXT_BYTES or base64.urlsafe_b64encode(raw).decode("ascii")!=s else None;sys.stdout.buffer.write(raw)'
)" \
python3 {PLUGIN_ROOT}/scripts/mst.py delegation start \
  --task-id "{task_id}" \
  --idempotency-key "{task_id}:start:{stable_key}" \
  --host "{host}" \
  --provider "{provider}" \
  --capability-status available \
  --route-reason "{route.reason_code}" \
  --worktree-dir "{worktree_path}" \
  --model "{model}" \
  --selector "{selector}" \
  {reasoning_effort_flag} \
  --scope "{scope}" \
  --prompt-file "{prompt_file}" \
  --output-path "{output_path}"

analysis|review|exploration|ideation|discussion|debug가 실제 read-only 작업이고 별도 linked worktree를 쓰지 않는 경우에만 --read-only를 추가한다. 구현·수정 작업에는 이 예외를 사용하지 않는다.

그 다음 parent invocation별 고유한 {claimant_id}로 single-use spawn claim을 요청한다. 오직 이 호출에서 spawn_allowed=true와 non-empty private claim_token_file을 함께 받은 단 한 caller만 native host tool을 한 번 호출할 수 있다. raw bearer token은 CLI JSON, argv, process listing, tool transcript, child prompt에 노출하지 않는다.

MST_SESSION_ID="{CANONICAL_MST_SESSION_ID}" \
MST_CONTEXT_JSON="$(
  MST_CONTEXT_B64="{CANONICAL_MST_CONTEXT_B64}" \
  python3 -c 'import base64,os,sys;s=os.environ["MST_CONTEXT_B64"];MAX_CONTEXT_BYTES=262144;sys.exit("encoded MST context exceeds limit") if len(s)>349528 else None;raw=base64.b64decode(s.encode("ascii"),altchars=b"-_",validate=True);sys.exit("decoded MST context is oversized or non-canonical") if len(raw)>MAX_CONTEXT_BYTES or base64.urlsafe_b64encode(raw).decode("ascii")!=s else None;sys.stdout.buffer.write(raw)'
)" \
python3 {PLUGIN_ROOT}/scripts/mst.py delegation claim-spawn \
  --task-id "{task_id}" \
  --attempt-id "{attempt_id}" \
  --claimant-id "{claimant_id}" \
  --idempotency-key "{task_id}:claim:{claimant_id}"

spawn_allowed=false, claim_status=claim_replay|already_claimed|reconciling|provider_task_in_flight|terminal, 빈 claim_token_file, 또는 claim 응답 유실/불명확 상태에서는 host tool을 호출하지 않는다. claim_replay|already_claimed는 winner의 claim lease가 살아 있는 동안 wait만 하며 recover/cancel로 ownership을 빼앗지 않는다. lease 만료 뒤에만 delegation recover로 reconcile하고, 그 외에는 next_action에 따라 기존 provider task에 attach/wait한다. claim exact replay는 bearer token/파일을 다시 발급하지 않는다. 따라서 claim 결과를 잃은 caller도 외부 fallback이나 중복 native spawn을 시도하지 않는다.

  • host=codex, provider=codex: Codex collaboration native tools를 사용한다. collaboration.spawn_agent로 spawn하고, resolved effort가 non-null이면 reasoning_effort를 전달한다. host가 제공하는 attach/follow-up 수단으로 같은 task에 연결하며, collaboration.wait_agent로 대기한 뒤 전달된 completion result를 수집한다. 병렬 fan-out은 독립 task마다 native agent를 하나씩 spawn한다.
  • host=claude, provider=claude: Claude의 Task(...) 또는 Agent(...) native tool로 spawn하고, resolved effort가 non-null이면 effort를 전달한다. background task는 host의 TaskOutput/resume 결과로 대기·수집한다.
  • 정상 same-host 경로에서 codex exec, claude CLI, mst.py run --provider {same_provider}, 같은 provider의 managed wrapper, 또는 nested /mst:claude//mst:codex를 호출하지 않는다.

Native tool 응답마다 claim winner parent가 다음 순서로 evidence를 기록한다. {claim_token_file}은 winner 응답의 mode 0400 private one-shot handle이며 acknowledge 성공 시 삭제된다. 내용을 읽거나 복사하거나 child/user/log에 전달하지 않는다. 각 명령의 JSON 응답에서 status/phase를 확인하고 blocked/reconciling이면 더 진행하지 않는다. 아래 인라인 lifecycle command도 생략된 python3 {PLUGIN_ROOT}/scripts/mst.py와 함께 문서 최상단의 정확한 MST_BOUND_SUBPROCESS 두 환경 변수 prefix를 매번 리터럴로 확장하며, bare command로 실행하지 않는다.

  1. spawn 성공 및 provider task ID 수신: delegation acknowledge --task-id "{task_id}" --attempt-id "{attempt_id}" --claim-token-file "{claim_token_file}" --spawn-status created_with_task_id --provider-task-id "{provider_task_id}" --idempotency-key "{task_id}:ack:{stable_key}"
  2. host task 연결 확인: delegation attach --task-id "{task_id}" --attempt-id "{attempt_id}" --attach-status attached --idempotency-key "{task_id}:attach:{stable_key}"
  3. 대기 중 주기적 생존 증거: delegation heartbeat --task-id "{task_id}" --attempt-id "{attempt_id}" --provider-state running --idempotency-key "{task_id}:heartbeat:{sequence}"
  4. host result 수집 직후 parent가 성공 결과의 비어 있지 않은 전체 내용을 bound {output_path}의 sibling temp file에 먼저 쓰고 atomic replace한 뒤, fresh hash/size를 확인한다. child에게 이 파일 쓰기를 맡기거나 기존 파일을 재사용하지 않는다.
  5. 결과 파일 evidence가 준비된 뒤에만: delegation complete --task-id "{task_id}" --attempt-id "{attempt_id}" --completion-signal "{succeeded|failed|timeout|unknown}" --output-path "{output_path}" --idempotency-key "{task_id}:complete:{stable_key}"

Native spawn이 task 생성 전에 명확히 실패한 경우에만 claim winner가 같은 --claim-token-file "{claim_token_file}"spawn-status=definitive_not_created를 acknowledge한 뒤 delegation fallback --expected-attempt-id "{attempt_id}" ...를 요청할 수 있다. 그 후 capability를 unavailable로 route planner에 다시 전달해 route=external을 받은 경우에만 external lane을 실행한다. claim 결과 유실, accepted, task ID 발급, attach 실패/timeout, child 실패, unknown/indeterminate 결과 뒤에는 external fallback을 금지하고 reconcile 상태를 유지한다.

2-A. External lane authorization

route=external 판정만으로 provider command를 직접 만들지 않는다. Fresh headless/cross-provider external lane은 command 생성 전에 중앙 planner 결과를 state에 고정한다.

MST_SESSION_ID="{CANONICAL_MST_SESSION_ID}" \
MST_CONTEXT_JSON="$(
  MST_CONTEXT_B64="{CANONICAL_MST_CONTEXT_B64}" \
  python3 -c 'import base64,os,sys;s=os.environ["MST_CONTEXT_B64"];MAX_CONTEXT_BYTES=262144;sys.exit("encoded MST context exceeds limit") if len(s)>349528 else None;raw=base64.b64decode(s.encode("ascii"),altchars=b"-_",validate=True);sys.exit("decoded MST context is oversized or non-canonical") if len(raw)>MAX_CONTEXT_BYTES or base64.urlsafe_b64encode(raw).decode("ascii")!=s else None;sys.stdout.buffer.write(raw)'
)" \
python3 {PLUGIN_ROOT}/scripts/mst.py dispatch authorize-external \
  --provider "{provider}" \
  --task-id "{task_id}" \
  --prompt-file "{prompt_file}" \
  --worktree-dir "{worktree_path}" \
  --running-log-path "{running_log}" \
  --trace-path "{trace_path}" \
  --output-path "{output_path}" \
  --model "{model}" \
  --selector "{selector}" \
  {reasoning_effort_flag} \
  --scope "{scope}" \
  --idempotency-key "{task_id}:external-authorize:{stable_key}" \
  {read_only_flag}

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
24
Forks
5
Last commit
Aug 2026
Advanced
Catalog kind
skill
Gateway key
agile
Source
github.com/myrtlepn/gran-maestro