From 31fe078e6429c87f7b3c887810aae6b8d607cf04 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Sat, 26 Sep 2026 00:18:03 -0700 Subject: [PATCH] opencode.json: an implement agent for unattended runs; run-plan uses it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From the combined Ornith guidance (ยง8.2), checked against OpenCode 1.18.30's schema and permission docs (last matching rule wins). Reads and edits outside the repository are denied, except OpenCode's own tool-output and temporary directories; web access, questions, subagents and doom-loop prompts are denied; edits to AGENTS.md, CLAUDE.md, opencode.json, the design documents, specs, plans and tools/ are denied; shell commands are allowed except push, reset, rebase, amend, switch, checkout, find /, locate, sudo, rm -rf / and curl/wget. A denied call continues the loop (experimental.continue_loop_on_deny, untested with Ornith). steps = 400 is a backstop. No custom prompt: OpenCode already reads AGENTS.md, and a prompt may replace its tool guidance. Co-Authored-By: Claude Opus 5.5 (1M context) --- opencode.json | 57 +++++++++++++++++++++++++++++++++++++++++++++++ tools/run-plan.sh | 8 ++++--- 2 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 opencode.json diff --git a/opencode.json b/opencode.json new file mode 100644 index 0000000..1326c5d --- /dev/null +++ b/opencode.json @@ -0,0 +1,57 @@ +{ + "$schema": "https://opencode.ai/config.json", + "autoupdate": false, + "share": "disabled", + "permission": { + "external_directory": { + "*": "deny", + "~/.local/share/opencode/tool-output/*": "allow", + "/var/folders/*/T/opencode/*": "allow", + "/tmp/opencode/*": "allow" + }, + "webfetch": "deny", + "websearch": "deny", + "question": "deny", + "task": "deny", + "doom_loop": "deny", + "edit": { + "*": "allow", + "AGENTS.md": "deny", + "CLAUDE.md": "deny", + "opencode.json": "deny", + "docs/design.md": "deny", + "docs/decisions.md": "deny", + "docs/inference-contract.md": "deny", + "docs/specs/**": "deny", + "docs/plans/**": "deny", + "tools/**": "deny" + }, + "bash": { + "*": "allow", + "git push*": "deny", + "git reset*": "deny", + "git rebase*": "deny", + "git commit --amend*": "deny", + "git switch*": "deny", + "git checkout*": "deny", + "find /*": "deny", + "locate *": "deny", + "sudo *": "deny", + "rm -rf /*": "deny", + "curl *": "deny", + "wget *": "deny" + } + }, + "experimental": { + "continue_loop_on_deny": true + }, + "agent": { + "implement": { + "description": "Implements exactly one closed task file from docs/plans, then commits and logs it.", + "mode": "primary", + "temperature": 0.6, + "top_p": 0.95, + "steps": 400 + } + } +} diff --git a/tools/run-plan.sh b/tools/run-plan.sh index 03d161d..9a34b3c 100755 --- a/tools/run-plan.sh +++ b/tools/run-plan.sh @@ -8,7 +8,9 @@ # OPENCODE_MODEL model to use (default straylight/laguna-s-2.1; OpenCode's own default differs). # The part after the provider (`ornith-1.5-35b-a3b`) names the model in each # commit's `Co-Authored-By: ` trailer and in the log. -# OPENCODE_FLAGS extra flags for `opencode run`, for example "--pure" or "--auto" +# OPENCODE_FLAGS extra flags for `opencode run`, for example "--pure" +# Each session runs as the `implement` agent of ./opencode.json: its permissions deny edits to the +# plans and design documents, reads outside the repository, subagents and pushes. # DRY_RUN=1 print what would be run and stop set -eu @@ -32,7 +34,7 @@ for task in "$plan"/[0-9][0-9]-*.md; do prompt="Read \`$task\` and do exactly that task. You are $agent: write $agent in the log row's Model column, and end the commit message with this line, exactly: $trailer" if [ "${DRY_RUN:-0}" = 1 ]; then - echo "opencode run -m $model ${OPENCODE_FLAGS:-} --title $milestone/$name \"$prompt\"" + echo "opencode run --agent implement -m $model ${OPENCODE_FLAGS:-} --title $milestone/$name \"$prompt\"" continue fi if [ -n "$(git status --porcelain)" ]; then @@ -44,7 +46,7 @@ for task in "$plan"/[0-9][0-9]-*.md; do echo "=== $milestone/$name: started $(date '+%H:%M:%S')" # A new `opencode run` is a new session: nothing carries over from the task before. # shellcheck disable=SC2086 - if ! opencode run -m "$model" ${OPENCODE_FLAGS:-} --title "$milestone/$name" "$prompt" \ + if ! opencode run --agent implement -m "$model" ${OPENCODE_FLAGS:-} --title "$milestone/$name" "$prompt" \ > "$runs/$name.log" 2>&1; then echo "run-plan: opencode exited with an error in $name; see $runs/$name.log" >&2 exit 1