From f0fd5575e157f0ba60304014aff4a62eaa8b0f9c Mon Sep 17 00:00:00 2001 From: admin Date: Mon, 18 May 2026 11:44:59 +0800 Subject: [PATCH] Initial release: aireng --- .claude-plugin/plugin.json | 42 +++++++ commands/aireng.md | 80 ++++++++++++++ scripts/aireng_mode.py | 184 +++++++++++++++++++++++++++++++ skills/aireng/SKILL.md | 65 +++++++++++ skills/aireng/agents/openai.yaml | 3 + 5 files changed, 374 insertions(+) create mode 100644 .claude-plugin/plugin.json create mode 100644 commands/aireng.md create mode 100644 scripts/aireng_mode.py create mode 100644 skills/aireng/SKILL.md create mode 100644 skills/aireng/agents/openai.yaml diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..c366547 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,42 @@ +{ + "name": "aireng", + "version": "0.6.0", + "description": "AirEng is the sole public Air scheduler plugin. It reads AirArc planning artifacts, dispatches isolated AirDo subagents, monitors them on a 5-minute cadence, and merges structured results back into AirPlan without defaulting to parent-thread coding.", + "author": { + "name": "14816", + "email": "noreply@example.com", + "url": "https://airlongdian.fun" + }, + "homepage": "https://airlongdian.fun/plugins/aireng", + "repository": "http://git.airlongdian.fun/admin/aireng.git", + "license": "MIT", + "keywords": [ + "air", + "aireng", + "scheduler", + "subagent", + "parallel" + ], + "skills": "./skills/", + "interface": { + "displayName": "AirEng", + "shortDescription": "Schedule and monitor isolated AirDo subagents from AirArc plans", + "longDescription": "AirEng is the sole public scheduler for the Air workflow. It reads AirArc execution artifacts from AirPlan, prepares isolated task handoffs, dispatches multiple AirDo worker subagents with bounded concurrency, monitors them every 5 minutes, repairs stalled flow when possible, and merges structured worker results without replaying long parent-thread context or defaulting to parent-thread coding.", + "developerName": "14816", + "category": "Productivity", + "capabilities": [ + "Interactive", + "Write" + ], + "websiteURL": "https://airlongdian.fun/plugins/aireng", + "privacyPolicyURL": "https://openai.com/policies/row-privacy-policy/", + "termsOfServiceURL": "https://openai.com/policies/row-terms-of-use/", + "defaultPrompt": [ + "Use AirEng to load AirArc execution artifacts from AirPlan and plan a bounded dispatch wave.", + "Use AirEng to launch isolated AirDo subagents in parallel, keep concurrency within the dispatch manifest, and re-check active workers every 300 seconds.", + "Use AirEng to merge finalized AirDo results, repair blocked or stalled tasks, and keep AirPlan documents synchronized without defaulting to parent-thread implementation." + ], + "brandColor": "#0F766E", + "screenshots": [] + } +} diff --git a/commands/aireng.md b/commands/aireng.md new file mode 100644 index 0000000..b9b2006 --- /dev/null +++ b/commands/aireng.md @@ -0,0 +1,80 @@ +--- +description: Run AirEng as the sole public Air scheduler that reads AirArc artifacts, dispatches isolated AirDo subagents, monitors them every 5 minutes, and merges structured results into AirPlan without defaulting to parent-thread coding +argument-hint: [run|status|plan|dispatch|monitor|merge|intervene] +allowed-tools: [Read, Glob, Grep, Bash, Write, Edit] +--- + +# /aireng + +Use AirEng as the only public execution scheduler when the workflow should stay thin in the parent thread and execute real work through isolated subagents. + +## Steps + +1. Parse `$ARGUMENTS`; default to `run` when empty. +2. Always operate from the current project root and store workflow artifacts under `AirPlan/`. +3. The runtime auto-bootstraps missing `AirPlan/` files on first startup and does not overwrite existing project artifacts. +4. AirEng is a scheduler and convergence engine, not a default coding worker: + - Prefer isolated `/airdo` execution for normal task implementation. + - Keep parent-thread work focused on planning-source selection, dispatch, monitoring, merge, repair, and document convergence. + - Treat direct parent-thread editing as a temporary unblock action only when a worker is hard-blocked and cannot self-recover. + - After any temporary intervention, return immediately to scheduler mode. +5. AirEng is authorized to autonomously decide commands and file edits when needed to keep development moving unattended, but that autonomy serves orchestration, repair, doc sync, and unblock actions first rather than long-running parent-thread implementation. +6. For `status`, run: + +```bash +python "$HOME/plugins/aireng/scripts/aireng_mode.py" --mode status --project . +``` + +7. For `plan`, run: + +```bash +python "$HOME/plugins/aireng/scripts/aireng_mode.py" --mode plan --project . +``` + +8. For `dispatch`, run: + +```bash +python "$HOME/plugins/aireng/scripts/aireng_mode.py" --mode dispatch --project . +``` + +Then read the generated dispatch manifest under `AirPlan/state/aireng/dispatch/`, open each `handoffPath`, and prepare one isolated AirDo worker subagent per task. After a worker finishes, treat its `workerStatePath` `resultPath` as canonical instead of re-reading the task-local template `result.json`. + +9. For `monitor`, run: + +```bash +python "$HOME/plugins/aireng/scripts/aireng_mode.py" --mode monitor --project . +``` + +Use this to perform one non-blocking scheduler inspection pass: merge ready results, detect stalled workers, prepare repair continuation, and update `nextAction` in `AirPlan/state/aireng/state.json`. + +10. For `intervene`, run: + +```bash +python "$HOME/plugins/aireng/scripts/aireng_mode.py" --mode intervene --project . +``` + +Use this only for hard blockers that AirEng cannot clear through ordinary monitoring, repair, or re-dispatch decisions. + +11. For `run`, do one full scheduler step: + - Ensure AirEng state exists with `enter` if needed. + - Prefer `AirPlan/state/airarc/reviews/execution-plan.json`; if stale or missing, refresh via `plan`. + - If no active wave exists, dispatch the next available parallel-safe wave. + - If active workers already exist, monitor them instead of re-dispatching blindly. + - Spawn one `worker` subagent per dispatched task with `fork_context=false` so contexts stay isolated. + - Pass only the project path plus the task handoff file content; do not fork the full parent conversation. + - Keep at most `recommendedConcurrency` workers active at once. + - Do not execute ordinary child-task implementation in the parent thread. + - Do not interrupt actionable workers for midpoint status checks. + - Refresh `AirPlan/todo.md` and the active dispatch block in `AirPlan/plan.md` when a wave starts so progress is visible during execution. + - When ready results appear, merge them through: + +```bash +python "$HOME/plugins/aireng/scripts/aireng_mode.py" --mode merge --project . --result +``` + +12. In unattended runs, keep the scheduler on a 5-minute monitoring cadence: + - Re-check active workers every 300 seconds. + - Continue dispatching later waves automatically when the current wave converges. + - Stop only when state reaches `completed` or a true user-decision blocker remains. + +13. Throughout execution, keep `AirPlan/todo.md`, `AirPlan/plan.md`, and any required ADR/C4 updates synchronized. AirDo proposes `documentUpdates`; AirEng owns applying them. diff --git a/scripts/aireng_mode.py b/scripts/aireng_mode.py new file mode 100644 index 0000000..ef08ea2 --- /dev/null +++ b/scripts/aireng_mode.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import sys +from pathlib import Path + + +def _add_lib_path() -> None: + root = Path(__file__).resolve().parents[3] + lib_path = root / "lib" + if str(lib_path) not in sys.path: + sys.path.insert(0, str(lib_path)) + + +_add_lib_path() + +from air_runtime.engine import ( + build_engine_plan, + dispatch_worker_group, + enter_engine, + intervene_engine, + merge_worker_result, + monitor_engine, + run_engine_once, + status_engine, +) +from air_runtime.paths import todo_path as workflow_todo_path +from air_runtime.project_bootstrap import ensure_project_bootstrap + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="AirEng public scheduler runtime") + parser.add_argument( + "--mode", + choices=["enter", "status", "plan", "dispatch", "merge", "monitor", "run", "intervene"], + default="status", + ) + parser.add_argument("--project", default=".") + parser.add_argument("--todo", default="") + parser.add_argument("--result", default="") + parser.add_argument("--dispatch-group", default="") + return parser.parse_args() + + +def main() -> None: + args = parse_args() + project_root = Path(args.project).expanduser().resolve() + ensure_project_bootstrap(project_root) + + if args.mode == "enter": + state_path, health = enter_engine(project_root) + print("aireng_mode=enabled") + print(f"project_root={project_root}") + print(f"state_path={state_path}") + for key, value in health.items(): + print(f"{key}={'ok' if value else 'missing'}") + return + + if args.mode == "status": + state = status_engine(project_root) + print(f"aireng_mode={'enabled' if state.get('enabled') else 'disabled'}") + print(f"project_root={project_root}") + print(f"engine_mode={state.get('engineMode', '')}") + print(f"active_wave_id={state.get('activeWaveId', '')}") + print(f"active_dispatch_path={state.get('activeDispatchPath', '')}") + print(f"active_worker_count={len(state.get('activeWorkers', []))}") + print(f"merged_results={len(state.get('mergedResults', []))}") + print(f"pending_global_updates={len(state.get('pendingGlobalUpdates', []))}") + print(f"planning_source={state.get('planningSource', '')}") + print(f"xdb_sessions={len(state.get('xdbSessions', []))}") + print(f"xdb_policy_enabled={state.get('xdbPolicy', {}).get('enabled')}") + print(f"debug_sessions={len(state.get('debugSessions', []))}") + print(f"debug_policy_enabled={state.get('debugPolicy', {}).get('enabled')}") + print(f"repair_attempts={len(state.get('repairAttempts', []))}") + print(f"active_repairs={state.get('activeRepairCount', 0)}") + print(f"repair_policy_enabled={state.get('repairPolicy', {}).get('enabled')}") + print(f"monitor_interval_seconds={state.get('monitoringPolicy', {}).get('checkIntervalSeconds', 0)}") + print(f"last_loop_at={state.get('lastLoopAt', '')}") + print(f"last_intervention_at={state.get('lastInterventionAt', '')}") + print(f"next_action={state.get('nextAction', '')}") + for key, value in state.get("artifactHealth", {}).items(): + print(f"{key}={'ok' if value else 'missing'}") + return + + if args.mode == "plan": + current_todo_path = Path(args.todo).expanduser().resolve() if args.todo else workflow_todo_path(project_root) + result = build_engine_plan(project_root, current_todo_path) + print("aireng_mode=planned") + print(f"project_root={project_root}") + print(f"todo_path={current_todo_path}") + print(f"plan_path={result['planPath']}") + print(f"plan_markdown_path={result['planMarkdownPath']}") + print(f"review_json_path={result['reviewJsonPath']}") + print(f"review_markdown_path={result['reviewMarkdownPath']}") + print(f"planning_source={result['planningSource']}") + print(f"review_source_path={result['reviewSourcePath']}") + print(f"selected_tasks={','.join(result['selectedTasks'])}") + print(f"parallel_group_count={result['parallelGroupCount']}") + print(f"conflict_count={result['conflictCount']}") + return + + if args.mode == "dispatch": + result = dispatch_worker_group(project_root, args.dispatch_group) + print("aireng_mode=dispatched") + print(f"project_root={project_root}") + print(f"dispatch_path={result['dispatchPath']}") + print(f"group_name={result['groupName']}") + print(f"wave_id={result['waveId']}") + print(f"task_ids={','.join(result['taskIds'])}") + print(f"recommended_concurrency={result['recommendedConcurrency']}") + return + + if args.mode == "monitor": + result = monitor_engine(project_root) + print("aireng_mode=monitored") + print(f"project_root={project_root}") + print(f"engine_mode={result['engineMode']}") + print(f"active_worker_count={result['activeWorkerCount']}") + print(f"ready_to_merge_count={result['readyToMergeCount']}") + print(f"merged_count={result['mergedCount']}") + print(f"stalled_count={result['stalledCount']}") + print(f"intervention_count={result['interventionCount']}") + print(f"blocked_task_count={result['blockedTaskCount']}") + print(f"repair_queue_path={result['repairQueuePath']}") + print(f"next_action={result['nextAction']}") + return + + if args.mode == "intervene": + result = intervene_engine(project_root) + print("aireng_mode=intervened") + print(f"project_root={project_root}") + print(f"engine_mode={result['engineMode']}") + print(f"stalled_count={result['stalledCount']}") + print(f"intervention_count={result['interventionCount']}") + print(f"blocked_task_count={result['blockedTaskCount']}") + print(f"next_action={result['nextAction']}") + return + + if args.mode == "run": + current_todo_path = Path(args.todo).expanduser().resolve() if args.todo else workflow_todo_path(project_root) + result = run_engine_once(project_root, current_todo_path) + print("aireng_mode=ran") + print(f"project_root={project_root}") + print(f"action={result['action']}") + print(f"steps={','.join(result['steps'])}") + print(f"plan_path={result['planPath']}") + print(f"engine_mode={result['engineMode']}") + print(f"next_action={result['nextAction']}") + if 'dispatchPath' in result: + print(f"dispatch_path={result['dispatchPath']}") + if 'waveId' in result: + print(f"wave_id={result['waveId']}") + if 'taskIds' in result: + print(f"task_ids={','.join(result['taskIds'])}") + if 'activeWorkerCount' in result: + print(f"active_worker_count={result['activeWorkerCount']}") + return + + if not args.result: + raise SystemExit("--result is required for merge mode") + + result_path = Path(args.result).expanduser().resolve() + merged = merge_worker_result(project_root, result_path) + print("aireng_mode=merged") + print(f"project_root={project_root}") + print(f"task_id={merged['taskId']}") + print(f"task_status={merged['status']}") + print(f"archived_result_path={merged['archivedResultPath']}") + print(f"pending_global_update_count={merged['pendingGlobalUpdateCount']}") + print(f"doc_queue_path={merged['docQueuePath']}") + print(f"repair_queue_path={merged['repairQueuePath']}") + print(f"todo_path={merged['todoPath']}") + print(f"applied_doc_path_count={merged['appliedDocPathCount']}") + print(f"xdb_session_count={merged['xdbSessionCount']}") + print(f"debug_session_count={merged['debugSessionCount']}") + print(f"repair_attempt_count={merged['repairAttemptCount']}") + print(f"repair_prepared={merged['repairPrepared']}") + print(f"repair_dispatch_path={merged['repairDispatchPath']}") + print(f"next_action={merged['nextAction']}") + + +if __name__ == "__main__": + main() diff --git a/skills/aireng/SKILL.md b/skills/aireng/SKILL.md new file mode 100644 index 0000000..8a6b738 --- /dev/null +++ b/skills/aireng/SKILL.md @@ -0,0 +1,65 @@ +--- +name: aireng +description: Sole public Air scheduler that reads AirArc execution artifacts, dispatches isolated AirDo subagents with bounded concurrency, monitors them on a 5-minute cadence, and merges structured results into AirPlan. +--- + +# AirEng + +## Role + +- Own the global execution contract in `AirPlan/`. +- Read AirArc review output before falling back to local todo analysis. +- Dispatch isolated AirDo subagents with `fork_context=false`. +- Monitor active workers, merge structured worker results, and keep the scheduler moving unattended. +- Own debug policy, XDB policy, repair policy, intervention policy, and global document convergence. +- Default to no parent-thread coding; use parent-thread edits only for short unblock actions that restore the scheduler. + +## Planning Source Order + +1. `AirPlan/state/airarc/reviews/execution-plan.json` +2. `AirPlan/state/airarc/reviews/parallel-review.json` +3. Engine fallback analysis of `AirPlan/todo.md` + +## Dispatch Contract + +- Generate the dispatch manifest under `AirPlan/state/aireng/dispatch/`. +- Respect `recommendedConcurrency`; do not flood the workspace with overlapping workers. +- Spawn one isolated AirDo subagent per task handoff. +- After a worker finishes, read its `workerStatePath` and use the `resultPath` recorded there as the canonical finalized result location. +- Pass only the task handoff and project path to each worker. Do not fork the full parent thread history. +- Do not interrupt actionable workers for midpoint status updates; let them continue through implementation and finalize unless they surface a real blocker. +- Keep parent-thread work limited to orchestration, monitoring, merge, repair, document convergence, and minimal unblock actions. +- Refresh `AirPlan/todo.md` and the active dispatch block in `AirPlan/plan.md` when a wave starts so execution progress is visible during the run. + +## Monitoring Contract + +- Store scheduler state in `AirPlan/state/aireng/state.json`. +- Track `engineMode`, `activeWaveId`, `activeDispatchPath`, `activeWorkers`, `monitoringPolicy`, `nextAction`, and `interventionHistory`. +- Use `monitoringPolicy.checkIntervalSeconds = 300` as the default cadence for unattended monitoring. +- Prefer re-dispatch, repair, debug, or other isolated recovery flows before direct intervention. +- Escalate to user decision only when a worker remains hard-blocked after the allowed intervention budget. + +## Merge Guarantees + +- Update task status and merge log in `AirPlan/todo.md`. +- Apply worker `documentUpdates`. +- Refresh engine-managed sync blocks in `AirPlan/AGENTS.md` and `AirPlan/docs/architecture/c4/module.md`. +- Track AirXDB sessions in `AirPlan/state/aireng/state.json`. +- Track debug sessions in `AirPlan/state/aireng/state.json`. +- Track repair attempts in `AirPlan/state/aireng/state.json`. +- Refuse a `done` merge when required global document updates are missing. +- Refuse a GUI-like `done` merge when successful AirXDB evidence is missing. +- Apply worker `documentUpdates` promptly so plan, ADR, and C4 changes do not lag behind completed slices. + +## Commands + +```bash +python "$HOME/plugins/aireng/scripts/aireng_mode.py" --mode enter --project +python "$HOME/plugins/aireng/scripts/aireng_mode.py" --mode status --project +python "$HOME/plugins/aireng/scripts/aireng_mode.py" --mode plan --project --todo +python "$HOME/plugins/aireng/scripts/aireng_mode.py" --mode dispatch --project [--dispatch-group ] +python "$HOME/plugins/aireng/scripts/aireng_mode.py" --mode monitor --project +python "$HOME/plugins/aireng/scripts/aireng_mode.py" --mode run --project [--todo ] +python "$HOME/plugins/aireng/scripts/aireng_mode.py" --mode intervene --project +python "$HOME/plugins/aireng/scripts/aireng_mode.py" --mode merge --project --result +``` diff --git a/skills/aireng/agents/openai.yaml b/skills/aireng/agents/openai.yaml new file mode 100644 index 0000000..83079c6 --- /dev/null +++ b/skills/aireng/agents/openai.yaml @@ -0,0 +1,3 @@ +name: aireng +short_description: Public Air scheduler for isolated AirDo subagent dispatch and AirPlan merges +default_prompt: "Use AirEng to read AirArc execution artifacts from AirPlan, dispatch isolated AirDo subagents with bounded concurrency, and merge structured worker results back into AirPlan. When dispatching workers, instruct them to continue through implementation and finalize without stopping for midpoint progress updates unless a real blocker or explicit user decision is required."