Let summoned @coilyco-ops Goose file/move issues (top use case: route issues out of the inbox) #376

Open
opened 2026-06-18 06:45:31 +00:00 by coilysiren · 2 comments
Owner

Why

The top-tier use case for @coilyco-ops is moving issues out of coilysiren/inbox - read an intake issue, decide the right repo, and re-file it there (the same triage/routing a human does by hand). That requires summoned Goose to write to trackers, not just advise.

Today it can't. The summon run (scripts/coilyco_ops_summon.py, #374) is deliberately advisory-only: its system prompt says "do not push, open PRs, or treat your checkout as something to ship - a human reads your reply and decides," and run_goose doesn't even put a Forgejo credential in Goose's environment. The bot token already has write:issue (it posts comments with it), but the Goose run has no authority and no tool to use it.

This issue is the controlled expansion: let summoned Goose create / move / annotate issues, scoped tightly, with the inbox-routing workflow as the first concrete recipe.

What

A summon like @coilyco-ops route this to the right repo (or @coilyco-ops file this in coilyco-flight-deck/ward) on an inbox issue results in:

  1. Goose reads the source issue (title, body, context).
  2. Picks the target repo (from the summon if named, else its own judgment over the known repos).
  3. Creates the issue in the target repo - carrying the original body, a backlink to the inbox issue, and original-author attribution.
  4. Annotates + closes the inbox issue with a pointer to the new home (or just cross-files and leaves it open - see open decisions).
  5. Replies on the summon thread with what it did and the new issue URL.

Idempotent: a re-summon must not double-file (check for an existing backlink / the <!-- coilyco-ops-summon --> marker before creating).

Capability changes required

  1. Give Goose a write path + auth. Two options:
    • (a) via coily - instruct Goose to run coily ops forgejo issue create --repo <owner/name> --title ... --body-file .... coily self-authenticates (the /forgejo/api-token, authors as coilysiren). No token plumbing. Simplest.
    • (b) via the bot token - pass COILYCO_OPS_TOKEN into run_goose's subprocess env= and have Goose hit the API directly (authors as coilyco-ops, which reads as "filed by automation").
    • Recommendation: (a) for least plumbing, unless author-as-bot attribution matters - decide below.
  2. Loosen the system prompt from advisory-only to an "issue steward" posture: may create/move/comment/label/close issues; still forbidden to push code, merge, force-push, or mutate main. The line moves from "never write anything" to "tracker writes yes, code/repo writes no."
  3. Depends on the Goose-profile fix in #375. The first live reply showed Goose loaded the apps/extensionmanager toolset and had no shell or file tools (Tool 'read' not found). With that profile it cannot run coily or read the repo at all, so issue-filing is impossible until Goose runs with the developer/shell extension (the --no-profile + pinned-extension approach the goose-triage harness uses). This is a hard prerequisite.

Security posture (this is a real expansion - call it out)

#374's whole NO-GO answer was "advisory only, blast radius one disposable clone, never mutates anything." This widens it to tracker mutations. The guardrails that keep it acceptable:

  • Allowlist-gated - only COILYCO_OPS_SUMMON_ALLOW authors can summon (currently just coilysiren), so the blast radius is "what an authorized human asked for."
  • write:issue only - no write:repository code paths, no main mutation, no admin. Goose can touch issues/comments/labels, nothing that ships.
  • No deletes - issue deletion needs site-admin, which the bot lacks. Destruction stays human. (Closing/moving is reversible.)
  • Logged - every create/close/move is an o11y event (ties into #375), so an unexpected write is visible.

Open decisions (flag, don't guess)

  • Author-as coilysiren (path a) or coilyco-ops (path b)?
  • On a successful move: auto-close the inbox issue, or cross-file and leave it open for a human to close?
  • Target-repo selection: only when explicitly named in the summon, or let Goose choose from the known repo set? (Explicit-only is safer to start.)
  • Label/milestone carry-over on the moved issue.

Done when

  • @coilyco-ops route this on an inbox issue files it in the correct repo with a backlink + attribution, annotates/closes the source, and replies with the new URL - end to end, logged.
  • The steward system prompt permits issue writes and still hard-blocks code/main mutation.
  • Re-summoning is idempotent (no double-file).

refs #374, #375

## Why The top-tier use case for `@coilyco-ops` is **moving issues out of `coilysiren/inbox`** - read an intake issue, decide the right repo, and re-file it there (the same triage/routing a human does by hand). That requires summoned Goose to **write to trackers**, not just advise. Today it can't. The summon run (`scripts/coilyco_ops_summon.py`, #374) is deliberately **advisory-only**: its system prompt says *"do not push, open PRs, or treat your checkout as something to ship - a human reads your reply and decides,"* and `run_goose` doesn't even put a Forgejo credential in Goose's environment. The bot *token* already has `write:issue` (it posts comments with it), but the Goose run has no authority and no tool to use it. This issue is the controlled expansion: let summoned Goose **create / move / annotate issues**, scoped tightly, with the inbox-routing workflow as the first concrete recipe. ## What A summon like `@coilyco-ops route this to the right repo` (or `@coilyco-ops file this in coilyco-flight-deck/ward`) on an inbox issue results in: 1. Goose reads the source issue (title, body, context). 2. Picks the target repo (from the summon if named, else its own judgment over the known repos). 3. **Creates the issue in the target repo** - carrying the original body, a backlink to the inbox issue, and original-author attribution. 4. **Annotates + closes the inbox issue** with a pointer to the new home (or just cross-files and leaves it open - see open decisions). 5. Replies on the summon thread with what it did and the new issue URL. Idempotent: a re-summon must not double-file (check for an existing backlink / the `<!-- coilyco-ops-summon -->` marker before creating). ## Capability changes required 1. **Give Goose a write path + auth.** Two options: - **(a) via `coily`** - instruct Goose to run `coily ops forgejo issue create --repo <owner/name> --title ... --body-file ...`. coily self-authenticates (the `/forgejo/api-token`, **authors as `coilysiren`**). No token plumbing. Simplest. - **(b) via the bot token** - pass `COILYCO_OPS_TOKEN` into `run_goose`'s subprocess `env=` and have Goose hit the API directly (**authors as `coilyco-ops`**, which reads as "filed by automation"). - Recommendation: (a) for least plumbing, unless author-as-bot attribution matters - decide below. 2. **Loosen the system prompt** from advisory-only to an **"issue steward"** posture: *may* create/move/comment/label/close issues; **still forbidden** to push code, merge, force-push, or mutate `main`. The line moves from "never write anything" to "tracker writes yes, code/repo writes no." 3. **Depends on the Goose-profile fix in #375.** The first live reply showed Goose loaded the `apps`/`extensionmanager` toolset and had *no shell or file tools* (`Tool 'read' not found`). With that profile it cannot run `coily` or read the repo at all, so issue-filing is impossible until Goose runs with the developer/shell extension (the `--no-profile` + pinned-extension approach the `goose-triage` harness uses). This is a hard prerequisite. ## Security posture (this is a real expansion - call it out) #374's whole NO-GO answer was "advisory only, blast radius one disposable clone, never mutates anything." This widens it to **tracker mutations**. The guardrails that keep it acceptable: - **Allowlist-gated** - only `COILYCO_OPS_SUMMON_ALLOW` authors can summon (currently just `coilysiren`), so the blast radius is "what an authorized human asked for." - **`write:issue` only** - no `write:repository` code paths, no `main` mutation, no admin. Goose can touch issues/comments/labels, nothing that ships. - **No deletes** - issue deletion needs site-admin, which the bot lacks. Destruction stays human. (Closing/moving is reversible.) - **Logged** - every create/close/move is an o11y event (ties into #375), so an unexpected write is visible. ## Open decisions (flag, don't guess) - Author-as `coilysiren` (path a) or `coilyco-ops` (path b)? - On a successful move: auto-close the inbox issue, or cross-file and leave it open for a human to close? - Target-repo selection: only when explicitly named in the summon, or let Goose choose from the known repo set? (Explicit-only is safer to start.) - Label/milestone carry-over on the moved issue. ## Done when - `@coilyco-ops route this` on an inbox issue files it in the correct repo with a backlink + attribution, annotates/closes the source, and replies with the new URL - end to end, logged. - The steward system prompt permits issue writes and still hard-blocks code/`main` mutation. - Re-summoning is idempotent (no double-file). refs #374, #375
Author
Owner

Decision (Kai): the write path is ward-kdl, authoring as the coilyco-ops bot. This resolves the "via coily vs via bot token" open question - both are dropped.

And it turns out the write surface already exists in ward-kdl, so this issue shrinks a lot. cmd/ward-kdl/ward-kdl.forgejo.guardfile.kdl (ward#151) already:

  • Authenticates every Forgejo op as coilyco-ops via SSM /forgejo/coilyco-ops/api-token (deliberately off the operator's personal PAT, so automated ops attribute to the bot - exactly what we want).
  • Ships issue create / edit / comment / close / reopen and issue-label add/set/remove, scoped to coily* owners (coilysiren/inbox matches coily*).
  • Ships an action move-issue <source owner/name> <index> <target owner/name> that is the inbox-routing recipe: view the source, create the issue in the target, comment the new URL back onto the source as a back-link, then close the source. Never deletes (mirrors never delete issue).

Confirmed live on this host: ward-kdl v0.70.0, ward-kdl ops forgejo action move-issue and ward-kdl ops forgejo issue create <owner> <repo> --title ... --body ... both resolve.

So the remaining work is only:

  1. Let summoned Goose shell out to ward-kdl ops forgejo ... - depends on the Goose-profile fix in #375 (today Goose loads the apps/extension toolset with no shell or file tools, so it cannot run ward-kdl at all). Hard prerequisite, unchanged. ward-kdl must also be on the Goose host's PATH.
  2. Steward system prompt - replace the advisory-only framing in run_goose with one that tells Goose it may route/file issues using ward-kdl ops forgejo action move-issue and issue create, while still forbidding code push / main mutation.

No new ward verb, no token plumbing, no coily-as-coilysiren. The "capability changes" section above is superseded by this: drop option (a)/(b), the path is ward-kdl as the bot, and the move-issue action already carries the recipe (label/milestone carry-over is the one piece it defers).

Open decisions still standing: explicit-target-only vs Goose repo-guessing, and whether to keep the source open after a cross-file (move-issue closes it by default).

**Decision (Kai): the write path is `ward-kdl`, authoring as the `coilyco-ops` bot.** This resolves the "via coily vs via bot token" open question - both are dropped. And it turns out the write surface **already exists** in ward-kdl, so this issue shrinks a lot. `cmd/ward-kdl/ward-kdl.forgejo.guardfile.kdl` (ward#151) already: - Authenticates every Forgejo op **as `coilyco-ops`** via SSM `/forgejo/coilyco-ops/api-token` (deliberately off the operator's personal PAT, so automated ops attribute to the bot - exactly what we want). - Ships `issue create / edit / comment / close / reopen` and `issue-label add/set/remove`, scoped to `coily*` owners (`coilysiren/inbox` matches `coily*`). - Ships an `action move-issue <source owner/name> <index> <target owner/name>` that **is** the inbox-routing recipe: view the source, create the issue in the target, comment the new URL back onto the source as a back-link, then close the source. Never deletes (mirrors `never delete issue`). Confirmed live on this host: `ward-kdl v0.70.0`, `ward-kdl ops forgejo action move-issue` and `ward-kdl ops forgejo issue create <owner> <repo> --title ... --body ...` both resolve. So the remaining work is **only**: 1. **Let summoned Goose shell out to `ward-kdl ops forgejo ...`** - depends on the Goose-profile fix in #375 (today Goose loads the apps/extension toolset with no shell or file tools, so it cannot run `ward-kdl` at all). Hard prerequisite, unchanged. `ward-kdl` must also be on the Goose host's PATH. 2. **Steward system prompt** - replace the advisory-only framing in `run_goose` with one that tells Goose it may route/file issues using `ward-kdl ops forgejo action move-issue` and `issue create`, while still forbidding code push / `main` mutation. No new ward verb, no token plumbing, no coily-as-`coilysiren`. The "capability changes" section above is superseded by this: drop option (a)/(b), the path is ward-kdl as the bot, and the move-issue action already carries the recipe (label/milestone carry-over is the one piece it defers). Open decisions still standing: explicit-target-only vs Goose repo-guessing, and whether to keep the source open after a cross-file (move-issue closes it by default).
Member

Triage: P3 / interactive - Goose file/move-issues feature; some design + agent-channel work.

(goose-style pass; claude as the judgment engine, applied 2026-06-23)

<!-- goose-triage --> **Triage:** `P3` / `interactive` - Goose file/move-issues feature; some design + agent-channel work. (goose-style pass; claude as the judgment engine, applied 2026-06-23)
Sign in to join this conversation.
No description provided.