Run NestQuest orchestration directly in the terminal (#151)
Co-authored-by: orchestrator-agent <orchestrator@cubecraftlabs.com> Co-committed-by: orchestrator-agent <orchestrator@cubecraftlabs.com>
This commit was merged in pull request #151.
This commit is contained in:
@@ -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.
|
|
||||||
@@ -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.
|
||||||
@@ -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.
|
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
---
|
---
|
||||||
description: Start the NestQuest development cycle on this host
|
description: Run the NestQuest orchestrator now in this OpenCode terminal
|
||||||
agent: cycle-control
|
agent: terminal-orchestrator
|
||||||
---
|
---
|
||||||
|
|
||||||
Run exactly this one shell command:
|
Run the NestQuest project queue now in this foreground OpenCode session.
|
||||||
|
Read the current CTXD prompt and Maestro state, but use the FOREGROUND
|
||||||
`/home/overseer/.local/share/nestquest-controller/scripts/nq-cycle start`
|
TERMINAL RUNTIME instead of the service runtime. Reconcile repository and
|
||||||
|
pull-request state, then execute CONTROLLER_TICK repeatedly until a global
|
||||||
Report its status. If it says an interactive NestQuest OpenCode session is
|
terminal condition, explicit user stop, genuine global block, or host-forced
|
||||||
open, tell me to close this session; the background controller will begin
|
yield. Do not start or wait for the systemd controller.
|
||||||
afterward. Do not launch a developer agent or a controller tick in this chat.
|
|
||||||
|
|||||||
@@ -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.
|
|
||||||
+37
-34
@@ -1,13 +1,23 @@
|
|||||||
# NestQuest controller
|
# NestQuest foreground orchestrator
|
||||||
|
|
||||||
OpenCode 1.18.31 loads the three named agents from `.opencode/agents/`. Their
|
OpenCode 1.18.32 loads the named agents from `.opencode/agents/`. Their
|
||||||
models match the current NestQuest CTXD prompt: DeepSeek V4.1
|
models match the current NestQuest CTXD prompt: DeepSeek V4.1 Flash for the
|
||||||
Flash for controller and approver, GLM 5.3 Flash for development. The
|
foreground orchestrator and approver, and GLM 5.3 Flash for development.
|
||||||
controller runs one OpenCode turn at a time, then starts a fresh turn based on
|
Run orchestration directly from a shell:
|
||||||
Maestro, CTXD, Git, and pull-request state. Its state file is
|
|
||||||
`~/.local/state/nestquest-controller/last-run.json` (mode 0600). A lock allows
|
```bash
|
||||||
only one controller process. It waits while an interactive OpenCode session is
|
nq-orchestrate
|
||||||
open for the NestQuest checkout.
|
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
|
`scripts/nq-agent` launches developer and approver sessions with fixed agent
|
||||||
definitions and no `--auto` flag. Headless OpenCode runs use `--pure` so the
|
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 output under `/tmp/opencode`, and posts the Gitea approval.
|
||||||
The approver verifies the reviewed head again before a merge.
|
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
|
at `/home/overseer/.local/share/nestquest-controller`; it does not depend on
|
||||||
which task branch is checked out in the primary NestQuest directory. Install
|
which task branch is checked out in the primary NestQuest directory. Install
|
||||||
that copy, run `scripts/nq-controller --check`, and verify all four OpenCode
|
that copy, run `scripts/nq-controller --check`, and verify the OpenCode
|
||||||
agents (orchestrator, developer, approver, cycle-control)
|
agents (terminal-orchestrator, developer, and approver)
|
||||||
with `opencode debug agent <name>` while `OPENCODE_CONFIG_DIR` points at the
|
with `opencode debug agent <name>` while `OPENCODE_CONFIG_DIR` points at the
|
||||||
installed `.opencode` directory. Install `deploy/nestquest-controller.service`
|
installed `.opencode` directory. Install `scripts/nq-orchestrate` in
|
||||||
under `~/.config/systemd/user/` and reload the user manager. Its lack of an
|
`~/.local/bin/`. For the global slash command, also install
|
||||||
`[Install]` section makes it a manual-only unit; development starts only
|
`.opencode/agents/terminal-orchestrator.md` under
|
||||||
when requested. Copy `deploy/opencode-global-commands/*.md` into
|
`~/.config/opencode/agents/` and
|
||||||
`~/.config/opencode/commands/` and `.opencode/agents/cycle-control.md` into
|
`deploy/opencode-global-commands/start-nestquest-development.md` under
|
||||||
`~/.config/opencode/agents/` for the commands to work from older branches.
|
`~/.config/opencode/commands/`. The old `nestquest-controller.service` remains static and
|
||||||
The host has lingering
|
inactive only for migration safety; foreground launch stops it.
|
||||||
enabled for the `overseer` user. The service waits for an open interactive
|
|
||||||
NestQuest session to close before starting work.
|
|
||||||
|
|
||||||
In NestQuest's OpenCode session, use `/start-development`,
|
Inside an already-open OpenCode TUI, `/start-development` (or the global
|
||||||
`/stop-development`, or `/development-status`. The matching global
|
`/start-nestquest-development`) switches to the same foreground
|
||||||
`/start-nestquest-development`, `/stop-nestquest-development`, and
|
`terminal-orchestrator` behavior immediately. The shell launcher is the
|
||||||
`/nestquest-development-status` commands work even while an older branch is
|
preferred entry point when supplying a mission prompt file.
|
||||||
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.
|
|
||||||
|
|
||||||
`templates/ORCHESTRATOR-PROMPT.template.md` and
|
`templates/ORCHESTRATOR-PROMPT.template.md` and
|
||||||
`templates/nestquest.profile.json` make the current CTXD cycle prompt
|
`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
|
account. Strong isolation requires separate OS identities and protected
|
||||||
credentials for the controller, reviewer, and approver.
|
credentials for the controller, reviewer, and approver.
|
||||||
|
|
||||||
A tick has an explicit six-hour timeout. On timeout or an OpenCode error, the
|
The terminal runtime repeatedly executes CONTROLLER_TICK within the foreground
|
||||||
next tick must reconcile external state before acting. Three consecutive
|
session. Neither a feature merge nor a progress report ends the run while
|
||||||
failed ticks stop the service. A BLOCKED verdict also stops it. QUIESCENT
|
eligible work remains. A host-forced yield returns a continuation checkpoint
|
||||||
causes a five-minute poll. Neither a feature merge nor a progress report
|
that can be resumed in the same OpenCode session.
|
||||||
stops the controller while eligible work remains.
|
|
||||||
|
|
||||||
Do not put tokens in this repository or in unit files. Existing OpenCode MCP
|
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
|
connections supply CTXD and Maestro. Rotate those MCP tokens as a coordinated
|
||||||
|
|||||||
@@ -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.
|
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
---
|
---
|
||||||
description: Start the NestQuest development cycle on this host
|
description: Run the NestQuest orchestrator now in this OpenCode terminal
|
||||||
agent: cycle-control
|
agent: terminal-orchestrator
|
||||||
---
|
---
|
||||||
|
|
||||||
Run exactly this one shell command:
|
Run the NestQuest project queue now in this foreground OpenCode session.
|
||||||
|
Read the current CTXD prompt and Maestro state, but use the FOREGROUND
|
||||||
`/home/overseer/.local/share/nestquest-controller/scripts/nq-cycle start`
|
TERMINAL RUNTIME instead of the service runtime. Reconcile repository and
|
||||||
|
pull-request state, then execute CONTROLLER_TICK repeatedly until a global
|
||||||
Report its status. If it says an interactive NestQuest OpenCode session is
|
terminal condition, explicit user stop, genuine global block, or host-forced
|
||||||
open, tell me to close this session; the background controller will begin
|
yield. Do not start or wait for the systemd controller.
|
||||||
afterward. Do not launch a developer agent or a controller tick in this chat.
|
|
||||||
|
|||||||
@@ -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.
|
|
||||||
@@ -22,9 +22,8 @@ class CyclePromptTests(unittest.TestCase):
|
|||||||
"RELEASE_BRANCH": "production",
|
"RELEASE_BRANCH": "production",
|
||||||
"CONTROL_HOME": "/opt/examplequest/controller",
|
"CONTROL_HOME": "/opt/examplequest/controller",
|
||||||
"SERVICE_NAME": "examplequest-controller.service",
|
"SERVICE_NAME": "examplequest-controller.service",
|
||||||
"START_COMMAND": "/start-examplequest-development",
|
"PROJECT_ROOT": "/srv/examplequest",
|
||||||
"STOP_COMMAND": "/stop-examplequest-development",
|
"TERMINAL_LAUNCHER": "eq-orchestrate",
|
||||||
"STATUS_COMMAND": "/examplequest-development-status",
|
|
||||||
"FEATURE_BRANCH_EXAMPLE": "feature/EQ-17",
|
"FEATURE_BRANCH_EXAMPLE": "feature/EQ-17",
|
||||||
})
|
})
|
||||||
output = RENDER(TEMPLATE, other)
|
output = RENDER(TEMPLATE, other)
|
||||||
@@ -44,6 +43,14 @@ class CyclePromptTests(unittest.TestCase):
|
|||||||
with self.assertRaisesRegex(ValueError, "PROJECT_NAME"):
|
with self.assertRaisesRegex(ValueError, "PROJECT_NAME"):
|
||||||
RENDER(TEMPLATE, other)
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -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
|
|
||||||
Executable
+52
@@ -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"
|
||||||
@@ -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
|
This section is the execution contract for a foreground OpenCode
|
||||||
precedence over Section 21 when deciding whether a single model invocation may
|
orchestrator. It replaces the service runtime section in the rendered prompt
|
||||||
end. The persistent user service runs one fresh, headless orchestrator turn per
|
and takes precedence over any service-specific runtime instructions later
|
||||||
controller tick. Section 21 governs mission completion, not the lifetime of
|
read from {{CONTEXT_STORE}}.
|
||||||
one OpenCode turn.
|
|
||||||
|
|
||||||
The user starts this project's cycle explicitly from OpenCode with
|
The user explicitly started this run with `{{TERMINAL_LAUNCHER}}`. Begin
|
||||||
`{{START_COMMAND}}`. `{{STOP_COMMAND}}` stops it, and
|
work immediately in `{{PROJECT_ROOT}}`; do not start, wait for, or delegate
|
||||||
`{{STATUS_COMMAND}}` reports its state. The `{{SERVICE_NAME}}` user unit is
|
control to `{{SERVICE_NAME}}`. This single OpenCode session owns the
|
||||||
not enabled at login. Reading this prompt in an ordinary interactive session
|
orchestration loop until a global terminal condition, an explicit user stop,
|
||||||
does not authorize starting the background service. The service waits for the
|
or a host-forced yield.
|
||||||
interactive project session to close before its first tick.
|
|
||||||
|
|
||||||
At the end of EVERY orchestrator invocation, emit exactly one final line:
|
Execute `CONTROLLER_TICK` repeatedly inside this foreground session. Do not
|
||||||
`ORCHESTRATOR_STATE: CONTINUE`, `ORCHESTRATOR_STATE: QUIESCENT`, or
|
end a turn merely to emit `ORCHESTRATOR_STATE: CONTINUE`, and do not treat
|
||||||
`ORCHESTRATOR_STATE: BLOCKED`. Use CONTINUE whenever eligible or in-flight
|
one task, review, pull request, or feature merge as completion. After every
|
||||||
work remains and state the next concrete action immediately above that line.
|
child result or external state change, reconcile {{CONTEXT_STORE}},
|
||||||
The service starts a fresh invocation after reconciling {{CONTEXT_STORE}}, {{WORK_TRACKER}}, Git,
|
{{WORK_TRACKER}}, Git, pull requests, and checks, then perform the next
|
||||||
pull requests, and checks. Use QUIESCENT only after verifying that no eligible
|
eligible action.
|
||||||
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.
|
|
||||||
|
|
||||||
The installed launchers live at
|
The installed role launchers live at `{{CONTROL_HOME}}/scripts/`. Use
|
||||||
`{{CONTROL_HOME}}/scripts/`. Use `{{ROLE_LAUNCHER}}`
|
`{{ROLE_LAUNCHER}}` for developer and approver sessions. Use
|
||||||
to start developer or approver OpenCode sessions. Use `{{REVIEW_LAUNCHER}}` with
|
`{{REVIEW_LAUNCHER}}` with WORKTREE, BASE_REF, HEAD_REF,
|
||||||
WORKTREE, BASE_REF, HEAD_REF, REVIEW_PROMPT_FILE, PR_NUMBER, OUTPUT_FILE to
|
REVIEW_PROMPT_FILE, PR_NUMBER, OUTPUT_FILE for a fresh read-only
|
||||||
run a fresh read-only {{REVIEWER_DISPLAY_NAME}} review at high effort. Its output file
|
{{REVIEWER_DISPLAY_NAME}} review. Review output belongs under
|
||||||
must be under `{{SCRATCH_DIR}}`; it posts a {{CODE_HOST}} review as `{{REVIEW_ACCOUNT}}`
|
`{{SCRATCH_DIR}}`, and exact-head approval must be posted by
|
||||||
only after approving the exact current head. The feature-to-{{INTEGRATION_BRANCH}} approver uses
|
`{{REVIEW_ACCOUNT}}`. The feature-to-{{INTEGRATION_BRANCH}} approver must
|
||||||
`{{MERGE_BROKER}}` and must verify that same exact-head {{CODE_HOST}} approval.
|
use `{{MERGE_BROKER}}`; the orchestrator and developer never perform that
|
||||||
No controller or developer session performs the feature-to-{{INTEGRATION_BRANCH}} merge.
|
merge.
|
||||||
|
|
||||||
The service holds one controller lock and waits while an interactive
|
If the host forces a yield before a global terminal condition, return the
|
||||||
{{PROJECT_NAME}} OpenCode session is open. Each model turn has a {{TICK_TIMEOUT_LABEL}} host
|
machine-readable checkpoint from Section 22. The user can resume the same
|
||||||
timeout. After an error or timeout, reconcile external state before retrying;
|
OpenCode session from the terminal. A yield is a checkpoint, not a claim that
|
||||||
three consecutive failed turns stop the service for inspection. A successful
|
the project queue is complete.
|
||||||
feature merge is a transition back into scheduling, not a terminal condition.
|
|
||||||
|
|||||||
+13
-5
@@ -16,9 +16,17 @@ section, especially task/feature semantics, release rules, permissions,
|
|||||||
review gates, and the host runtime section. The template assumes an OpenCode
|
review gates, and the host runtime section. The template assumes an OpenCode
|
||||||
controller, a feature/task tracker, a versioned context store, a code host,
|
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
|
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
|
For NestQuest, `nq-orchestrate --print-prompt` emits the fully rendered
|
||||||
with its version check. Give the project its own agent definitions, service
|
foreground prompt without starting OpenCode. `nq-orchestrate [mission.md]`
|
||||||
unit, broker, and OpenCode start/stop commands. The template itself does not
|
renders that same prompt, appends the optional mission, and supplies it to the
|
||||||
start a controller or register another project.
|
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.
|
||||||
|
|||||||
@@ -21,12 +21,11 @@
|
|||||||
"CODE_HOST": "Gitea",
|
"CODE_HOST": "Gitea",
|
||||||
"TASK_ID_EXAMPLE": "TASK-123",
|
"TASK_ID_EXAMPLE": "TASK-123",
|
||||||
"FEATURE_BRANCH_EXAMPLE": "feature/NQ-17-family-calendar",
|
"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",
|
"CONTROL_HOME": "/home/overseer/.local/share/nestquest-controller",
|
||||||
"SERVICE_NAME": "nestquest-controller.service",
|
"SERVICE_NAME": "nestquest-controller.service",
|
||||||
"START_COMMAND": "/start-nestquest-development",
|
"TERMINAL_LAUNCHER": "nq-orchestrate",
|
||||||
"STOP_COMMAND": "/stop-nestquest-development",
|
|
||||||
"STATUS_COMMAND": "/nestquest-development-status",
|
|
||||||
"ROLE_LAUNCHER": "nq-agent",
|
"ROLE_LAUNCHER": "nq-agent",
|
||||||
"REVIEW_LAUNCHER": "nq-review",
|
"REVIEW_LAUNCHER": "nq-review",
|
||||||
"MERGE_BROKER": "nq-approve-merge",
|
"MERGE_BROKER": "nq-approve-merge",
|
||||||
|
|||||||
Reference in New Issue
Block a user