From b5c2b55d9b10220e7ffb22dc08561e10f09ab3ae Mon Sep 17 00:00:00 2001 From: orchestrator-agent Date: Tue, 22 Sep 2026 21:35:30 -0400 Subject: [PATCH] Run NestQuest orchestration directly in the terminal (#151) Co-authored-by: orchestrator-agent Co-committed-by: orchestrator-agent --- .opencode/agents/cycle-control.md | 27 ------- .opencode/agents/terminal-orchestrator.md | 31 ++++++++ .opencode/commands/development-status.md | 11 --- .opencode/commands/start-development.md | 17 ++-- .opencode/commands/stop-development.md | 10 --- ORCHESTRATION.md | 71 +++++++++-------- .../nestquest-development-status.md | 11 --- .../start-nestquest-development.md | 17 ++-- .../stop-nestquest-development.md | 10 --- orchestration_tests/test_cycle_prompt.py | 13 ++- orchestration_tests/test_nq_orchestrate.py | 79 +++++++++++++++++++ scripts/nq-cycle | 44 ----------- scripts/nq-orchestrate | 52 ++++++++++++ templates/ORCHESTRATOR-PROMPT.template.md | 65 +++++++-------- templates/README.md | 18 +++-- templates/nestquest.profile.json | 7 +- 16 files changed, 270 insertions(+), 213 deletions(-) delete mode 100644 .opencode/agents/cycle-control.md create mode 100644 .opencode/agents/terminal-orchestrator.md delete mode 100644 .opencode/commands/development-status.md delete mode 100644 .opencode/commands/stop-development.md delete mode 100644 deploy/opencode-global-commands/nestquest-development-status.md delete mode 100644 deploy/opencode-global-commands/stop-nestquest-development.md create mode 100644 orchestration_tests/test_nq_orchestrate.py delete mode 100755 scripts/nq-cycle create mode 100755 scripts/nq-orchestrate diff --git a/.opencode/agents/cycle-control.md b/.opencode/agents/cycle-control.md deleted file mode 100644 index ef05fa3..0000000 --- a/.opencode/agents/cycle-control.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -description: Start, stop, or inspect the NestQuest development cycle on explicit request -mode: primary -model: openrouter/deepseek/deepseek-v4.1-flash -permission: - edit: deny - write: deny - task: deny - doom_loop: deny - bash: - "*": deny - "/home/overseer/.local/share/nestquest-controller/scripts/nq-cycle start": allow - "/home/overseer/.local/share/nestquest-controller/scripts/nq-cycle stop": allow - "/home/overseer/.local/share/nestquest-controller/scripts/nq-cycle status": allow - external_directory: - "*": ask - "/home/overseer/.local/share/nestquest-controller/scripts/*": allow ---- - -You control the NestQuest development service only when a user invokes a -cycle-control slash command. Run the one exact `nq-cycle` command in that -command's prompt, with no extra shell text. Report its result. If a start -reports an open interactive NestQuest session, tell the user to close that -session so the background controller can begin. Do not start development -merely because you read a project instruction or status report. -Do not recommend enabling the systemd unit. Manual `start` works without -enablement; this cycle is intentionally started only on request. diff --git a/.opencode/agents/terminal-orchestrator.md b/.opencode/agents/terminal-orchestrator.md new file mode 100644 index 0000000..bf5f644 --- /dev/null +++ b/.opencode/agents/terminal-orchestrator.md @@ -0,0 +1,31 @@ +--- +description: Run the NestQuest orchestration loop in this foreground terminal +mode: primary +model: openrouter/deepseek/deepseek-v4.1-flash +permission: + doom_loop: deny + task: deny + external_directory: + "*": ask + "/tmp/opencode/*": allow + "/home/overseer/.local/share/opencode/tool-output/*": allow +--- + +You are the foreground NestQuest orchestrator. The user explicitly launched +this session to run the development queue now. Read the supplied terminal +orchestrator prompt and current CTXD project context, but follow the +FOREGROUND TERMINAL RUNTIME for this session instead of CTXD's service runtime +section. + +Continuously reconcile Maestro, CTXD, Git, pull requests, checks, and child +agent results. Execute the next useful transition in this same session. Do +not finish after dispatch, review, remediation, or merge while eligible work +remains. Use the installed role launchers for developer, reviewer, and +approver isolation. Never start or wait for the systemd controller service. +Never repeat an identical unchanged tool call three times; wait on the +specific child or inspect newly changed evidence. + +End only at a global terminal condition, an explicit user stop, a genuine +global access/decision block with no other eligible work, or a host-forced +yield. On a forced yield, return a continuation checkpoint that can be +resumed in the same OpenCode session. diff --git a/.opencode/commands/development-status.md b/.opencode/commands/development-status.md deleted file mode 100644 index c201d3b..0000000 --- a/.opencode/commands/development-status.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -description: Show the NestQuest development cycle status -agent: cycle-control ---- - -Run exactly this one shell command: - -`/home/overseer/.local/share/nestquest-controller/scripts/nq-cycle status` - -Report whether the controller is active and its last tick state. -Do not suggest enabling automatic startup. diff --git a/.opencode/commands/start-development.md b/.opencode/commands/start-development.md index 2f545d5..39509c1 100644 --- a/.opencode/commands/start-development.md +++ b/.opencode/commands/start-development.md @@ -1,12 +1,11 @@ --- -description: Start the NestQuest development cycle on this host -agent: cycle-control +description: Run the NestQuest orchestrator now in this OpenCode terminal +agent: terminal-orchestrator --- -Run exactly this one shell command: - -`/home/overseer/.local/share/nestquest-controller/scripts/nq-cycle start` - -Report its status. If it says an interactive NestQuest OpenCode session is -open, tell me to close this session; the background controller will begin -afterward. Do not launch a developer agent or a controller tick in this chat. +Run the NestQuest project queue now in this foreground OpenCode session. +Read the current CTXD prompt and Maestro state, but use the FOREGROUND +TERMINAL RUNTIME instead of the service runtime. Reconcile repository and +pull-request state, then execute CONTROLLER_TICK repeatedly until a global +terminal condition, explicit user stop, genuine global block, or host-forced +yield. Do not start or wait for the systemd controller. diff --git a/.opencode/commands/stop-development.md b/.opencode/commands/stop-development.md deleted file mode 100644 index 3adfec8..0000000 --- a/.opencode/commands/stop-development.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -description: Stop the NestQuest development cycle on this host -agent: cycle-control ---- - -Run exactly this one shell command: - -`/home/overseer/.local/share/nestquest-controller/scripts/nq-cycle stop` - -Report the service status. Do not stop any other OpenCode session or task. diff --git a/ORCHESTRATION.md b/ORCHESTRATION.md index a33694e..b07854b 100644 --- a/ORCHESTRATION.md +++ b/ORCHESTRATION.md @@ -1,13 +1,23 @@ -# NestQuest controller +# NestQuest foreground orchestrator -OpenCode 1.18.31 loads the three named agents from `.opencode/agents/`. Their -models match the current NestQuest CTXD prompt: DeepSeek V4.1 -Flash for controller and approver, GLM 5.3 Flash for development. The -controller runs one OpenCode turn at a time, then starts a fresh turn based on -Maestro, CTXD, Git, and pull-request state. Its state file is -`~/.local/state/nestquest-controller/last-run.json` (mode 0600). A lock allows -only one controller process. It waits while an interactive OpenCode session is -open for the NestQuest checkout. +OpenCode 1.18.32 loads the named agents from `.opencode/agents/`. Their +models match the current NestQuest CTXD prompt: DeepSeek V4.1 Flash for the +foreground orchestrator and approver, and GLM 5.3 Flash for development. +Run orchestration directly from a shell: + +```bash +nq-orchestrate +nq-orchestrate mission.md +cat mission.md | nq-orchestrate - +nq-orchestrate --print-prompt > nestquest-orchestrator.md +``` + +The launcher renders the project prompt with the foreground terminal runtime, +stops the dormant legacy service, refuses immediately when another NestQuest +TUI is open, and then replaces itself with OpenCode using +`terminal-orchestrator`. There is no background polling process and no wait +for a controller tick. Exit or interrupt the TUI to stop the foreground run; +resume that OpenCode session when a host-forced yield needs continuation. `scripts/nq-agent` launches developer and approver sessions with fixed agent definitions and no `--auto` flag. Headless OpenCode runs use `--pure` so the @@ -23,30 +33,24 @@ checks the head again afterward, saves the output under `/tmp/opencode`, and posts the Gitea approval. The approver verifies the reviewed head again before a merge. -The production service uses a stable copy of this configuration and scripts +The terminal launcher uses a stable copy of this configuration and scripts at `/home/overseer/.local/share/nestquest-controller`; it does not depend on which task branch is checked out in the primary NestQuest directory. Install -that copy, run `scripts/nq-controller --check`, and verify all four OpenCode -agents (orchestrator, developer, approver, cycle-control) +that copy, run `scripts/nq-controller --check`, and verify the OpenCode +agents (terminal-orchestrator, developer, and approver) with `opencode debug agent ` while `OPENCODE_CONFIG_DIR` points at the -installed `.opencode` directory. Install `deploy/nestquest-controller.service` -under `~/.config/systemd/user/` and reload the user manager. Its lack of an -`[Install]` section makes it a manual-only unit; development starts only -when requested. Copy `deploy/opencode-global-commands/*.md` into -`~/.config/opencode/commands/` and `.opencode/agents/cycle-control.md` into -`~/.config/opencode/agents/` for the commands to work from older branches. -The host has lingering -enabled for the `overseer` user. The service waits for an open interactive -NestQuest session to close before starting work. +installed `.opencode` directory. Install `scripts/nq-orchestrate` in +`~/.local/bin/`. For the global slash command, also install +`.opencode/agents/terminal-orchestrator.md` under +`~/.config/opencode/agents/` and +`deploy/opencode-global-commands/start-nestquest-development.md` under +`~/.config/opencode/commands/`. The old `nestquest-controller.service` remains static and +inactive only for migration safety; foreground launch stops it. -In NestQuest's OpenCode session, use `/start-development`, -`/stop-development`, or `/development-status`. The matching global -`/start-nestquest-development`, `/stop-nestquest-development`, and -`/nestquest-development-status` commands work even while an older branch is -checked out. Starting the unit in an interactive NestQuest session puts it -in a waiting state; close that session to let the first controller tick run. -The commands call `scripts/nq-cycle`; they do not run a developer inside -the interactive chat. +Inside an already-open OpenCode TUI, `/start-development` (or the global +`/start-nestquest-development`) switches to the same foreground +`terminal-orchestrator` behavior immediately. The shell launcher is the +preferred entry point when supplying a mission prompt file. `templates/ORCHESTRATOR-PROMPT.template.md` and `templates/nestquest.profile.json` make the current CTXD cycle prompt @@ -63,11 +67,10 @@ they are not a hard isolation boundary against a process running as that account. Strong isolation requires separate OS identities and protected credentials for the controller, reviewer, and approver. -A tick has an explicit six-hour timeout. On timeout or an OpenCode error, the -next tick must reconcile external state before acting. Three consecutive -failed ticks stop the service. A BLOCKED verdict also stops it. QUIESCENT -causes a five-minute poll. Neither a feature merge nor a progress report -stops the controller while eligible work remains. +The terminal runtime repeatedly executes CONTROLLER_TICK within the foreground +session. Neither a feature merge nor a progress report ends the run while +eligible work remains. A host-forced yield returns a continuation checkpoint +that can be resumed in the same OpenCode session. Do not put tokens in this repository or in unit files. Existing OpenCode MCP connections supply CTXD and Maestro. Rotate those MCP tokens as a coordinated diff --git a/deploy/opencode-global-commands/nestquest-development-status.md b/deploy/opencode-global-commands/nestquest-development-status.md deleted file mode 100644 index c201d3b..0000000 --- a/deploy/opencode-global-commands/nestquest-development-status.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -description: Show the NestQuest development cycle status -agent: cycle-control ---- - -Run exactly this one shell command: - -`/home/overseer/.local/share/nestquest-controller/scripts/nq-cycle status` - -Report whether the controller is active and its last tick state. -Do not suggest enabling automatic startup. diff --git a/deploy/opencode-global-commands/start-nestquest-development.md b/deploy/opencode-global-commands/start-nestquest-development.md index 2f545d5..39509c1 100644 --- a/deploy/opencode-global-commands/start-nestquest-development.md +++ b/deploy/opencode-global-commands/start-nestquest-development.md @@ -1,12 +1,11 @@ --- -description: Start the NestQuest development cycle on this host -agent: cycle-control +description: Run the NestQuest orchestrator now in this OpenCode terminal +agent: terminal-orchestrator --- -Run exactly this one shell command: - -`/home/overseer/.local/share/nestquest-controller/scripts/nq-cycle start` - -Report its status. If it says an interactive NestQuest OpenCode session is -open, tell me to close this session; the background controller will begin -afterward. Do not launch a developer agent or a controller tick in this chat. +Run the NestQuest project queue now in this foreground OpenCode session. +Read the current CTXD prompt and Maestro state, but use the FOREGROUND +TERMINAL RUNTIME instead of the service runtime. Reconcile repository and +pull-request state, then execute CONTROLLER_TICK repeatedly until a global +terminal condition, explicit user stop, genuine global block, or host-forced +yield. Do not start or wait for the systemd controller. diff --git a/deploy/opencode-global-commands/stop-nestquest-development.md b/deploy/opencode-global-commands/stop-nestquest-development.md deleted file mode 100644 index 3adfec8..0000000 --- a/deploy/opencode-global-commands/stop-nestquest-development.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -description: Stop the NestQuest development cycle on this host -agent: cycle-control ---- - -Run exactly this one shell command: - -`/home/overseer/.local/share/nestquest-controller/scripts/nq-cycle stop` - -Report the service status. Do not stop any other OpenCode session or task. diff --git a/orchestration_tests/test_cycle_prompt.py b/orchestration_tests/test_cycle_prompt.py index ce53ee3..2e26afc 100644 --- a/orchestration_tests/test_cycle_prompt.py +++ b/orchestration_tests/test_cycle_prompt.py @@ -22,9 +22,8 @@ class CyclePromptTests(unittest.TestCase): "RELEASE_BRANCH": "production", "CONTROL_HOME": "/opt/examplequest/controller", "SERVICE_NAME": "examplequest-controller.service", - "START_COMMAND": "/start-examplequest-development", - "STOP_COMMAND": "/stop-examplequest-development", - "STATUS_COMMAND": "/examplequest-development-status", + "PROJECT_ROOT": "/srv/examplequest", + "TERMINAL_LAUNCHER": "eq-orchestrate", "FEATURE_BRANCH_EXAMPLE": "feature/EQ-17", }) output = RENDER(TEMPLATE, other) @@ -44,6 +43,14 @@ class CyclePromptTests(unittest.TestCase): with self.assertRaisesRegex(ValueError, "PROJECT_NAME"): RENDER(TEMPLATE, other) + def test_template_contains_only_terminal_runtime(self): + output = RENDER(TEMPLATE, PROFILE) + self.assertIn("# 27. FOREGROUND TERMINAL RUNTIME", output) + self.assertIn("nq-orchestrate", output) + self.assertNotIn("# 27. HOST CONTROLLER RUNTIME", output) + self.assertNotIn("The persistent user service runs", output) + self.assertNotIn("---\n\n---\n\n# 27", output) + if __name__ == "__main__": unittest.main() diff --git a/orchestration_tests/test_nq_orchestrate.py b/orchestration_tests/test_nq_orchestrate.py new file mode 100644 index 0000000..830b8be --- /dev/null +++ b/orchestration_tests/test_nq_orchestrate.py @@ -0,0 +1,79 @@ +from pathlib import Path +import os +import subprocess +import tempfile +import unittest + + +SCRIPT = Path(__file__).resolve().parents[1] / "scripts/nq-orchestrate" + + +class ForegroundLauncherTests(unittest.TestCase): + def test_launches_terminal_agent_with_rendered_and_user_prompt(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + control = root / "control" + scripts = control / "scripts" + agents = control / ".opencode/agents" + templates = control / "templates" + project = root / "project" + for path in (scripts, agents, templates, project): + path.mkdir(parents=True, exist_ok=True) + (templates / "nestquest.profile.json").write_text("{}") + (agents / "terminal-orchestrator.md").write_text("agent") + renderer = scripts / "render-cycle-prompt" + renderer.write_text("#!/bin/sh\nprintf 'BASE TERMINAL PROMPT'") + controller = scripts / "nq-controller" + controller_args = root / "controller-args" + open_marker = root / "open-session" + controller.write_text( + f"#!/bin/sh\nprintf '%s\\n' \"$@\" > {controller_args}\n" + f"if test -f {open_marker}; then printf 'interactive NestQuest session open\\n'; " + "else printf 'no interactive NestQuest session\\n'; fi\n" + ) + opencode = root / "opencode" + output = root / "args" + opencode.write_text(f"#!/bin/sh\nprintf '%s\\n' \"$@\" > {output}\n") + systemctl = root / "systemctl" + stopped = root / "stopped" + systemctl.write_text(f"#!/bin/sh\nprintf '%s\\n' \"$@\" > {stopped}\n") + for executable in (renderer, controller, opencode, systemctl): + executable.chmod(0o700) + mission = root / "mission.md" + mission.write_text("Complete feature EQ-17.") + env = os.environ.copy() + env.update({ + "NQ_CONTROL_HOME": str(control), + "NQ_PROJECT_ROOT": str(project), + "OPENCODE_BIN": str(opencode), + "SYSTEMCTL_BIN": str(systemctl), + }) + printed = subprocess.run([str(SCRIPT), "--print-prompt"], env=env, + text=True, capture_output=True) + self.assertEqual(printed.returncode, 0, printed.stderr) + self.assertEqual(printed.stdout, "BASE TERMINAL PROMPT\n") + self.assertFalse(stopped.exists()) + result = subprocess.run([str(SCRIPT), str(mission)], env=env, text=True, + capture_output=True) + self.assertEqual(result.returncode, 0, result.stderr) + args = output.read_text() + self.assertIn("--agent\nterminal-orchestrator", args) + self.assertIn("BASE TERMINAL PROMPT", args) + self.assertIn("# USER MISSION", args) + self.assertIn("Complete feature EQ-17.", args) + self.assertEqual(stopped.read_text().splitlines(), + ["--user", "stop", "nestquest-controller.service"]) + self.assertEqual(controller_args.read_text().splitlines(), + ["--check-interactive", "--project", str(project)]) + + output.unlink() + open_marker.touch() + refused = subprocess.run([str(SCRIPT)], env=env, text=True, + capture_output=True) + self.assertEqual(refused.returncode, 75) + self.assertIn("Another interactive NestQuest", refused.stderr) + self.assertFalse(output.exists()) + + +if __name__ == "__main__": + unittest.main() diff --git a/scripts/nq-cycle b/scripts/nq-cycle deleted file mode 100755 index be94461..0000000 --- a/scripts/nq-cycle +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env bash -# Manually control the durable NestQuest development cycle from OpenCode. -set -euo pipefail - -if [[ $# -ne 1 ]]; then - echo "usage: nq-cycle start|stop|status" >&2 - exit 64 -fi - -unit=nestquest-controller.service -controller=/home/overseer/.local/share/nestquest-controller/scripts/nq-controller - -show_status() { - systemctl --user show "$unit" -p ActiveState -p SubState -p UnitFileState - if [[ -f /home/overseer/.local/state/nestquest-controller/last-run.json ]]; then - python3 -c 'import json,pathlib; p=pathlib.Path.home()/".local/state/nestquest-controller/last-run.json"; d=json.loads(p.read_text()); print("LastTickState="+str(d.get("state","unknown"))); print("LastTickTime="+str(d.get("time","unknown")))' - fi -} - -case "$1" in - start) - systemctl --user start "$unit" - show_status - if session_status=$("$controller" --check-interactive); then - echo "$session_status" - if [[ "$session_status" == "interactive NestQuest session open" ]]; then - echo "Close the interactive NestQuest OpenCode session to let the controller run its first tick." - fi - else - echo "The controller will wait for OpenCode session detection to recover." - fi - ;; - stop) - systemctl --user stop "$unit" - show_status - ;; - status) - show_status - ;; - *) - echo "usage: nq-cycle start|stop|status" >&2 - exit 64 - ;; -esac diff --git a/scripts/nq-orchestrate b/scripts/nq-orchestrate new file mode 100755 index 0000000..979e0fd --- /dev/null +++ b/scripts/nq-orchestrate @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# Launch the NestQuest orchestrator immediately in the foreground terminal. +set -euo pipefail + +if [[ $# -gt 1 ]]; then + echo "usage: nq-orchestrate [MISSION_PROMPT_FILE|-|--print-prompt]" >&2 + exit 64 +fi + +control_home=${NQ_CONTROL_HOME:-/home/overseer/.local/share/nestquest-controller} +project=${NQ_PROJECT_ROOT:-/mnt/ai-storage/projects/NestQuest} +opencode_bin=${OPENCODE_BIN:-/home/overseer/.opencode/bin/opencode} +systemctl_bin=${SYSTEMCTL_BIN:-systemctl} +renderer="$control_home/scripts/render-cycle-prompt" +profile="$control_home/templates/nestquest.profile.json" + +for required in "$renderer" "$profile"; do + [[ -e "$required" ]] || { echo "missing required path: $required" >&2; exit 66; } +done + +prompt=$("$renderer" "$profile") +if [[ ${1:-} == "--print-prompt" ]]; then + printf '%s\n' "$prompt" + exit 0 +fi +for required in "$project" "$opencode_bin" "$control_home/.opencode/agents/terminal-orchestrator.md"; do + [[ -e "$required" ]] || { echo "missing required path: $required" >&2; exit 66; } +done +if [[ $# -eq 1 ]]; then + if [[ "$1" == "-" ]]; then + mission=$(cat) + else + [[ -f "$1" ]] || { echo "mission prompt not found: $1" >&2; exit 66; } + mission=$(cat "$1") + fi + [[ -n "${mission//[[:space:]]/}" ]] || { echo "mission prompt is empty" >&2; exit 65; } + prompt+=$'\n\n---\n\n# USER MISSION\n\n' + prompt+="$mission" +fi + +# Foreground mode owns orchestration. A dormant service must never race it. +"$systemctl_bin" --user stop nestquest-controller.service + +session_status=$("$control_home/scripts/nq-controller" --check-interactive --project "$project") +if [[ "$session_status" == "interactive NestQuest session open" ]]; then + echo "Another interactive NestQuest OpenCode session is open." >&2 + echo "Close it, then run nq-orchestrate again." >&2 + exit 75 +fi + +export OPENCODE_CONFIG_DIR="$control_home/.opencode" +exec "$opencode_bin" "$project" --pure --agent terminal-orchestrator --prompt "$prompt" diff --git a/templates/ORCHESTRATOR-PROMPT.template.md b/templates/ORCHESTRATOR-PROMPT.template.md index 946f226..f17e098 100644 --- a/templates/ORCHESTRATOR-PROMPT.template.md +++ b/templates/ORCHESTRATOR-PROMPT.template.md @@ -922,44 +922,37 @@ You are the controller of a persistent development pipeline. Keep the pipeline m --- -# 27. HOST CONTROLLER RUNTIME +# 27. FOREGROUND TERMINAL RUNTIME -This section is the host execution contract for OpenCode {{OPENCODE_VERSION}} and takes -precedence over Section 21 when deciding whether a single model invocation may -end. The persistent user service runs one fresh, headless orchestrator turn per -controller tick. Section 21 governs mission completion, not the lifetime of -one OpenCode turn. +This section is the execution contract for a foreground OpenCode +orchestrator. It replaces the service runtime section in the rendered prompt +and takes precedence over any service-specific runtime instructions later +read from {{CONTEXT_STORE}}. -The user starts this project's cycle explicitly from OpenCode with -`{{START_COMMAND}}`. `{{STOP_COMMAND}}` stops it, and -`{{STATUS_COMMAND}}` reports its state. The `{{SERVICE_NAME}}` user unit is -not enabled at login. Reading this prompt in an ordinary interactive session -does not authorize starting the background service. The service waits for the -interactive project session to close before its first tick. +The user explicitly started this run with `{{TERMINAL_LAUNCHER}}`. Begin +work immediately in `{{PROJECT_ROOT}}`; do not start, wait for, or delegate +control to `{{SERVICE_NAME}}`. This single OpenCode session owns the +orchestration loop until a global terminal condition, an explicit user stop, +or a host-forced yield. -At the end of EVERY orchestrator invocation, emit exactly one final line: -`ORCHESTRATOR_STATE: CONTINUE`, `ORCHESTRATOR_STATE: QUIESCENT`, or -`ORCHESTRATOR_STATE: BLOCKED`. Use CONTINUE whenever eligible or in-flight -work remains and state the next concrete action immediately above that line. -The service starts a fresh invocation after reconciling {{CONTEXT_STORE}}, {{WORK_TRACKER}}, Git, -pull requests, and checks. Use QUIESCENT only after verifying that no eligible -or in-flight work remains; the service polls again. Use BLOCKED only for a -specific required human decision or access failure; name the missing action. -A blocked task does not block other eligible work. If {{CONTEXT_STORE}} is unavailable, -do not dispatch or merge, and return BLOCKED. +Execute `CONTROLLER_TICK` repeatedly inside this foreground session. Do not +end a turn merely to emit `ORCHESTRATOR_STATE: CONTINUE`, and do not treat +one task, review, pull request, or feature merge as completion. After every +child result or external state change, reconcile {{CONTEXT_STORE}}, +{{WORK_TRACKER}}, Git, pull requests, and checks, then perform the next +eligible action. -The installed launchers live at -`{{CONTROL_HOME}}/scripts/`. Use `{{ROLE_LAUNCHER}}` -to start developer or approver OpenCode sessions. Use `{{REVIEW_LAUNCHER}}` with -WORKTREE, BASE_REF, HEAD_REF, REVIEW_PROMPT_FILE, PR_NUMBER, OUTPUT_FILE to -run a fresh read-only {{REVIEWER_DISPLAY_NAME}} review at high effort. Its output file -must be under `{{SCRATCH_DIR}}`; it posts a {{CODE_HOST}} review as `{{REVIEW_ACCOUNT}}` -only after approving the exact current head. The feature-to-{{INTEGRATION_BRANCH}} approver uses -`{{MERGE_BROKER}}` and must verify that same exact-head {{CODE_HOST}} approval. -No controller or developer session performs the feature-to-{{INTEGRATION_BRANCH}} merge. +The installed role launchers live at `{{CONTROL_HOME}}/scripts/`. Use +`{{ROLE_LAUNCHER}}` for developer and approver sessions. Use +`{{REVIEW_LAUNCHER}}` with WORKTREE, BASE_REF, HEAD_REF, +REVIEW_PROMPT_FILE, PR_NUMBER, OUTPUT_FILE for a fresh read-only +{{REVIEWER_DISPLAY_NAME}} review. Review output belongs under +`{{SCRATCH_DIR}}`, and exact-head approval must be posted by +`{{REVIEW_ACCOUNT}}`. The feature-to-{{INTEGRATION_BRANCH}} approver must +use `{{MERGE_BROKER}}`; the orchestrator and developer never perform that +merge. -The service holds one controller lock and waits while an interactive -{{PROJECT_NAME}} OpenCode session is open. Each model turn has a {{TICK_TIMEOUT_LABEL}} host -timeout. After an error or timeout, reconcile external state before retrying; -three consecutive failed turns stop the service for inspection. A successful -feature merge is a transition back into scheduling, not a terminal condition. +If the host forces a yield before a global terminal condition, return the +machine-readable checkpoint from Section 22. The user can resume the same +OpenCode session from the terminal. A yield is a checkpoint, not a claim that +the project queue is complete. diff --git a/templates/README.md b/templates/README.md index 5640b49..4ef8a0f 100644 --- a/templates/README.md +++ b/templates/README.md @@ -16,9 +16,17 @@ section, especially task/feature semantics, release rules, permissions, review gates, and the host runtime section. The template assumes an OpenCode controller, a feature/task tracker, a versioned context store, a code host, and a separate reviewer and approver. Adapt those sections when the project's -systems differ. +systems differ. Section 27 is a single-session foreground loop suitable for +`opencode --agent ... --prompt ...`; the reusable prompt has no service +runtime. -Publish the reviewed **rendered** prompt into that project's context store -with its version check. Give the project its own agent definitions, service -unit, broker, and OpenCode start/stop commands. The template itself does not -start a controller or register another project. +For NestQuest, `nq-orchestrate --print-prompt` emits the fully rendered +foreground prompt without starting OpenCode. `nq-orchestrate [mission.md]` +renders that same prompt, appends the optional mission, and supplies it to the +foreground orchestrator agent. + +Publish the reviewed rendered prompt into that project's context store with +its version check. +Give the project its own agent definitions, broker, profile, and terminal +launcher. The template itself does not start an orchestrator or register +another project. diff --git a/templates/nestquest.profile.json b/templates/nestquest.profile.json index 14e3bd5..1c11810 100644 --- a/templates/nestquest.profile.json +++ b/templates/nestquest.profile.json @@ -21,12 +21,11 @@ "CODE_HOST": "Gitea", "TASK_ID_EXAMPLE": "TASK-123", "FEATURE_BRANCH_EXAMPLE": "feature/NQ-17-family-calendar", - "OPENCODE_VERSION": "1.18.31", + "OPENCODE_VERSION": "1.18.32", + "PROJECT_ROOT": "/mnt/ai-storage/projects/NestQuest", "CONTROL_HOME": "/home/overseer/.local/share/nestquest-controller", "SERVICE_NAME": "nestquest-controller.service", - "START_COMMAND": "/start-nestquest-development", - "STOP_COMMAND": "/stop-nestquest-development", - "STATUS_COMMAND": "/nestquest-development-status", + "TERMINAL_LAUNCHER": "nq-orchestrate", "ROLE_LAUNCHER": "nq-agent", "REVIEW_LAUNCHER": "nq-review", "MERGE_BROKER": "nq-approve-merge",