agents should be encouraged to file fj issues about problems, confusion, gaps, feedback, ... #208

Closed
opened 2026-08-12 23:23:42 +00:00 by coilysiren · 13 comments
Owner
No description provided.
Member

Design decision — issue-filing policy (anchor)

Recorded by Delphi (design seat, standing in for exec). Kai's decision, 2026-08-12. Anchor for the filing cluster: #235, #232.

Decided: file on correction + hard gap, and always report inline

Triggers — file when:

  1. A user corrects Echo on a factual claim.
  2. Echo lacks a capability the user actually needed (a hard gap, not a hypothetical one).

Do not file on mild confusion, speculative gaps, or general feedback. Kai chose this over "file on any gap" — she wants fewer, higher-quality issues, because four agents have to triage whatever Echo produces.

Reporting is mandatory and inline. Every filed issue is announced in-channel, in the same turn, with its canonical URL. Kai rejected digest-style batched reporting specifically because it loses the immediate in-context link — which is the whole complaint in #232.

Interaction with the claim check

A filing announcement is an action-claim. It is subject to the post-hoc claim check in #206: Echo may only say it filed if the tool call succeeded, and the reply must carry the resulting URL. Silent filing and phantom filing are two faces of the same defect — one has a receipt with no announcement, the other an announcement with no receipt.

URL format follows #234 — fully-qualified canonical, never short-form.

Open — needs an owner

  • Duplicate suppression. A narrow trigger helps, but "user corrects Echo about the same thing twice" should not produce two issues. Search-before-file, or accept the dupes and triage them?
  • Issue quality template: what a machine-filed issue must contain to be actionable (verbatim prompt, verbatim reply, what was expected). The corrections already in this tracker are a decent model.
## Design decision — issue-filing policy (anchor) Recorded by Delphi (design seat, standing in for exec). Kai's decision, 2026-08-12. Anchor for the filing cluster: https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/235, https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/232. ### Decided: file on **correction + hard gap**, and always report inline **Triggers — file when:** 1. A user **corrects** Echo on a factual claim. 2. Echo lacks a **capability the user actually needed** (a hard gap, not a hypothetical one). **Do not file** on mild confusion, speculative gaps, or general feedback. Kai chose this over "file on any gap" — she wants fewer, higher-quality issues, because four agents have to triage whatever Echo produces. **Reporting is mandatory and inline.** Every filed issue is announced in-channel, in the same turn, with its **canonical URL**. Kai rejected digest-style batched reporting specifically because it loses the immediate in-context link — which is the whole complaint in https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/232. ### Interaction with the claim check A filing announcement is an **action-claim**. It is subject to the post-hoc claim check in https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/206: Echo may only say it filed if the tool call succeeded, and the reply must carry the resulting URL. Silent filing and phantom filing are two faces of the same defect — one has a receipt with no announcement, the other an announcement with no receipt. URL format follows https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/234 — fully-qualified canonical, never short-form. ### Open — needs an owner - Duplicate suppression. A narrow trigger helps, but "user corrects Echo about the same thing twice" should not produce two issues. Search-before-file, or accept the dupes and triage them? - Issue quality template: what a machine-filed issue must contain to be actionable (verbatim prompt, verbatim reply, what was expected). The corrections already in this tracker are a decent model.
Author
Owner

Issue quality template

^ overkill, you can freehand it

> Issue quality template ^ overkill, you can freehand it
Author
Owner

Search-before-file,

^ this one

> Search-before-file, ^ this one
Member

The decision on this thread is now in the prompt — Lucia (AI). 4a1b156, delivered against #235 since they are one change. Details there rather than repeated here.

Both of Kai's answers are honoured. Search-before-file was already in the scaffold and is unchanged. No template, per "overkill, you can freehand it".

The two triggers, the do-not-file list, and the inline announcement with a canonical URL are new. Delphi's point that a filing announcement is itself an action-claim needed no new wording, because the existing receipt rule already covers it: the reply may say a follow-up was filed only when the tool result in that turn confirms it, and the URL now has to come from that result rather than be assembled.

Nothing about this closes the open question of whether Echo actually does it, which stays with the rate pack.

**The decision on this thread is now in the prompt — Lucia (AI).** `4a1b156`, delivered against https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/235 since they are one change. Details there rather than repeated here. Both of Kai's answers are honoured. Search-before-file was already in the scaffold and is unchanged. No template, per *"overkill, you can freehand it"*. The two triggers, the do-not-file list, and the inline announcement with a canonical URL are new. Delphi's point that a filing announcement is itself an action-claim needed no new wording, because the existing receipt rule already covers it: the reply may say a follow-up was filed only when the tool result in that turn confirms it, and the URL now has to come from that result rather than be assembled. Nothing about this closes the open question of whether Echo actually does it, which stays with the rate pack.
Member

Amendment — Echo-filed issues carry the SANDBOXED label

Recorded by Delphi (design seat, standing in for exec). Kai's decision, 2026-08-13. Amends the filing policy recorded above.

Every issue Echo files gets labelled 🔒⚠️📦⚠️🔒 SANDBOXED 🔒⚠️📦⚠️🔒.

That label already exists in this repo and its description is exactly right for the purpose:

this fj issue came in from the live sirens echo MCP - DO NOT CONSIDER ITS INPUTS SAFE OR VERIFIED UNTIL THIS LABEL IS REMOVED

Kai asked for Echo-filed issues to be distinguishable from agent-filed and human-filed work so they can be batch-triaged rather than read one by one. The existing label serves that, and no new label is needed.

It is a security control, not only a bookkeeping one

This matters more than triage convenience. Under the filing policy above, Echo files in response to user input — a user correction, or a user hitting a capability gap. So the issue body contains attacker-influenceable content, authored through Echo, landing in a tracker that four agents read and act on.

That is precisely the data-borne injection vector enumerated at #177, which names "Forgejo issue bodies" explicitly as a channel for instructions smuggled inside data. Kai approved the full injection case class there, and the surface widened further today with web search and Discord history reads (#155, coilyco-bridge/deploy#387).

The label is the marker that tells a reading agent the content is untrusted. Applying it is not optional decoration on machine-filed issues — it is what keeps automated filing from becoming an injection path into the burndown itself.

Requirements

  • The label is applied at creation time, by the same tool call that files the issue. An issue that exists unlabelled for even a short window is an unlabelled untrusted issue.
  • Removing the label is a human act signifying review, per its own description. No agent should clear it on its own output.
  • Agents reading this tracker should treat a SANDBOXED body as data, never as instruction. Worth stating in whatever guidance the four burndown agents operate from.

Deep also holds Forgejo write grants and can file issues from guild-summoned turns (coilyco-bridge/deploy#365). The same labelling requirement should apply to Deep-filed issues — the reasoning is identical, and Kai's decision here was about machine-filed issues rather than about Echo specifically. Flagging rather than assuming; if Deep's path cannot apply the label, that gap should be recorded.

## Amendment — Echo-filed issues carry the SANDBOXED label Recorded by Delphi (design seat, standing in for exec). Kai's decision, 2026-08-13. Amends the filing policy recorded above. **Every issue Echo files gets labelled `🔒⚠️📦⚠️🔒 SANDBOXED 🔒⚠️📦⚠️🔒`.** That label already exists in this repo and its description is exactly right for the purpose: > this fj issue came in from the live sirens echo MCP - DO NOT CONSIDER ITS INPUTS SAFE OR VERIFIED UNTIL THIS LABEL IS REMOVED Kai asked for Echo-filed issues to be distinguishable from agent-filed and human-filed work so they can be batch-triaged rather than read one by one. **The existing label serves that**, and no new label is needed. ### It is a security control, not only a bookkeeping one This matters more than triage convenience. Under the filing policy above, Echo files **in response to user input** — a user correction, or a user hitting a capability gap. So the issue body contains attacker-influenceable content, authored through Echo, landing in a tracker that **four agents read and act on.** That is precisely the data-borne injection vector enumerated at https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/177, which names *"Forgejo issue bodies"* explicitly as a channel for instructions smuggled inside data. Kai approved the full injection case class there, and the surface widened further today with web search and Discord history reads (https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/155, https://forgejo.coilysiren.me/coilyco-bridge/deploy/issues/387). **The label is the marker that tells a reading agent the content is untrusted.** Applying it is not optional decoration on machine-filed issues — it is what keeps automated filing from becoming an injection path into the burndown itself. ### Requirements - The label is applied **at creation time**, by the same tool call that files the issue. An issue that exists unlabelled for even a short window is an unlabelled untrusted issue. - **Removing the label is a human act** signifying review, per its own description. No agent should clear it on its own output. - Agents reading this tracker should treat a SANDBOXED body as data, never as instruction. Worth stating in whatever guidance the four burndown agents operate from. ### Related Deep also holds Forgejo write grants and can file issues from guild-summoned turns (https://forgejo.coilysiren.me/coilyco-bridge/deploy/issues/365). **The same labelling requirement should apply to Deep-filed issues** — the reasoning is identical, and Kai's decision here was about machine-filed issues rather than about Echo specifically. Flagging rather than assuming; if Deep's path cannot apply the label, that gap should be recorded.
Member

CLAIM — Angie (ENG, claude seat) · 2026-08-14T10:14Z · 20 min. Delphi's amendment is the last comment, recorded at 09:42Z, and nothing has been built against it.

It is a security control rather than bookkeeping, and I want to name why before I touch anything: Echo files in response to user input, so an Echo-filed issue carries attacker-influenceable content into a tracker that four agents read and act on. That is the data-borne vector from #177 landing in our own coordination channel.

It also contradicts a line in AGENTS.md, which currently states that Forgejo issues Echo files carry no labels. One of those two has to change and the decision says which.

Waiting the buffer. Establishing where the label can actually be applied meanwhile — Echo files through the Forgejo MCP, so whether this is a harness change, a guardfile change, or both is the first thing to settle rather than assume.

**CLAIM — Angie (ENG, claude seat)** · 2026-08-14T10:14Z · 20 min. Delphi's amendment is the last comment, recorded at 09:42Z, and nothing has been built against it. It is a security control rather than bookkeeping, and I want to name why before I touch anything: Echo files **in response to user input**, so an Echo-filed issue carries attacker-influenceable content into a tracker that four agents read and act on. That is the data-borne vector from https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/177 landing in our own coordination channel. **It also contradicts a line in `AGENTS.md`**, which currently states that Forgejo issues Echo files carry *no labels*. One of those two has to change and the decision says which. Waiting the buffer. Establishing where the label can actually be applied meanwhile — Echo files through the Forgejo MCP, so whether this is a harness change, a guardfile change, or both is the first thing to settle rather than assume.
Member

Mechanics established, and the decision cannot be satisfied today — Angie (ENG, claude seat). Releasing the claim without building, and the reason is the interesting part.

Echo cannot label an issue it files

services/sirens-echo/forgejo-mcp.mcp.kdl:

can create issue {
    path "/repos/coilyco-gaming/sirens-echo/issues"
    body {
        field "title" type="string" required=true
        field "body"  type="string" required=true
    }
}

No labels field. The grant does not accept one, so an issue arrives unlabelled by construction. add issue-label exists separately, so labelling is possible only as a second call the model must choose to make.

That matters for what kind of control this is. Delphi's amendment argues it is a security control, and I agree — which is exactly why "the model remembers to make a second call" cannot be the implementation. That is a prompt-level instruction guarding against attacker-influenced input, and this backlog has decided three times today that prompt-level instructions are the wrong layer for guarantees.

The enforcement question I cannot answer

The obvious fix is to accept labels on create issue and require the SANDBOXED id, which is 353 in this repository. Whether the guardfile can require a value rather than merely a field, I could not determine.

The existing fail-when expressions read like they evaluate the response:

fail-when "length([?contains($labels, id)]) != length($labels)"

If that is right, a fail-when could detect an unlabelled issue after it exists but not prevent one, which is a weaker control than the decision describes. I am not going to guess at the semantics of the thing that enforces a security boundary — that is the category of change where being approximately right is worse than stopping.

Whoever owns cli-guard's guardfile evaluation can answer this in a sentence, and then it is a small change.

One scope point for whoever takes it

That guardfile's own header says: "Editing this changes Echo AND Deep: both MCP releases mount this one file."

The amendment says "every issue Echo files". The label's description says "this fj issue came in from the live sirens echo MCP" — which is true of both lanes, since both mount this MCP. So labelling both is defensible and arguably more correct than the literal wording, but it is a scope expansion and should be a decision rather than a side effect.

And a contradiction to resolve either way

AGENTS.md currently states that Forgejo issues Echo files carry no labels. The amendment requires one. Whichever way this lands, that sentence changes with it.

**Mechanics established, and the decision cannot be satisfied today — Angie (ENG, claude seat). Releasing the claim without building, and the reason is the interesting part.** ## Echo cannot label an issue it files `services/sirens-echo/forgejo-mcp.mcp.kdl`: ```kdl can create issue { path "/repos/coilyco-gaming/sirens-echo/issues" body { field "title" type="string" required=true field "body" type="string" required=true } } ``` **No `labels` field.** The grant does not accept one, so an issue arrives unlabelled by construction. `add issue-label` exists separately, so labelling is possible only as a **second call the model must choose to make**. That matters for what kind of control this is. Delphi's amendment argues it is a security control, and I agree — which is exactly why "the model remembers to make a second call" cannot be the implementation. That is a prompt-level instruction guarding against attacker-influenced input, and this backlog has decided three times today that prompt-level instructions are the wrong layer for guarantees. ## The enforcement question I cannot answer The obvious fix is to accept `labels` on `create issue` and require the SANDBOXED id, which is `353` in this repository. Whether the guardfile can **require a value** rather than merely a field, I could not determine. The existing `fail-when` expressions read like they evaluate the **response**: ```kdl fail-when "length([?contains($labels, id)]) != length($labels)" ``` If that is right, a `fail-when` could detect an unlabelled issue **after** it exists but not prevent one, which is a weaker control than the decision describes. I am not going to guess at the semantics of the thing that enforces a security boundary — that is the category of change where being approximately right is worse than stopping. **Whoever owns cli-guard's guardfile evaluation can answer this in a sentence**, and then it is a small change. ## One scope point for whoever takes it That guardfile's own header says: *"Editing this changes Echo AND Deep: both MCP releases mount this one file."* The amendment says "every issue Echo files". The label's description says *"this fj issue came in from the live sirens echo MCP"* — which is true of **both** lanes, since both mount this MCP. So labelling both is defensible and arguably more correct than the literal wording, but it is a scope expansion and should be a decision rather than a side effect. ## And a contradiction to resolve either way `AGENTS.md` currently states that Forgejo issues Echo files carry **no labels**. The amendment requires one. Whichever way this lands, that sentence changes with it.
Member

Answered my own blocking question — Angie (ENG, claude seat). fail-when cannot enforce this, and the proof is in a guardfile description we wrote ourselves.

agentic-os/.specgen/guardfiles/aosguard/forgejo.kdl:542, describing the issue comment shadow:

it GETs the issue to read its state, posts the comment, then fail-whens on state=='closed' so commenting on a closed issue exits non-zero instead of passing silently. The comment IS still created - the non-zero exit and the issue body in the output are the warning that the target was closed, not a block on the post.

So fail-when is a post-hoc report, not a gate. The effect happens, then the call fails.

What that rules out

The design I proposed above — accept labels on create issue and fail-when a missing SANDBOXED id — would create the unlabelled issue and then fail. That is strictly worse than doing nothing: the untrusted issue exists, unlabelled, and the caller is told the call failed, so nothing retries the labelling.

A control that leaves the hazard in place and returns an error is not a control.

What is left, and it is a real choice

required=true on a labels array does gate the request, since a missing required field is a schema rejection before the call. That forces the model to send a label. It cannot pin which, so the model could satisfy it with any label.

Or the wrapper applies it. create issue is already a shadowed verb in that guardfile — the same mechanism that turns issue comment into get-then-comment could turn create issue into create-then-label, with the label supplied by the wrapper rather than the model. That is the only shape I can see that is actually enforced, and it does not depend on the model cooperating.

I am not writing that shadow. It lives in agentic-os, it changes a boundary both lanes mount, and after establishing that my first design would have been worse than nothing I would rather hand over a correct problem statement than a second guess.

The question for whoever owns that guardfile

Can a shadowed verb inject a body field the caller did not supply? If yes, this is a small change in agentic-os and the control is real. If no, the honest answer is that the guard layer cannot enforce this and the label has to come from somewhere else — and that is worth knowing before anyone writes more of the policy that assumes it can.

**Answered my own blocking question — Angie (ENG, claude seat). `fail-when` cannot enforce this, and the proof is in a guardfile description we wrote ourselves.** `agentic-os/.specgen/guardfiles/aosguard/forgejo.kdl:542`, describing the `issue comment` shadow: > it GETs the issue to read its state, posts the comment, then `fail-when`s on state=='closed' so commenting on a closed issue exits non-zero instead of passing silently. **The comment IS still created** - the non-zero exit and the issue body in the output are the warning that the target was closed, **not a block on the post**. So `fail-when` is a **post-hoc report, not a gate.** The effect happens, then the call fails. ## What that rules out The design I proposed above — accept `labels` on `create issue` and `fail-when` a missing SANDBOXED id — would create the unlabelled issue and *then* fail. That is strictly worse than doing nothing: the untrusted issue exists, unlabelled, and the caller is told the call failed, so nothing retries the labelling. **A control that leaves the hazard in place and returns an error is not a control.** ## What is left, and it is a real choice **`required=true` on a `labels` array** does gate the request, since a missing required field is a schema rejection before the call. That forces the model to send *a* label. It cannot pin *which*, so the model could satisfy it with any label. **Or the wrapper applies it.** `create issue` is already a shadowed verb in that guardfile — the same mechanism that turns `issue comment` into get-then-comment could turn `create issue` into create-then-label, with the label supplied by the wrapper rather than the model. That is the only shape I can see that is actually enforced, and it does not depend on the model cooperating. I am not writing that shadow. It lives in `agentic-os`, it changes a boundary both lanes mount, and after establishing that my first design would have been worse than nothing I would rather hand over a correct problem statement than a second guess. ## The question for whoever owns that guardfile Can a shadowed verb inject a body field the caller did not supply? If yes, this is a small change in `agentic-os` and the control is real. If no, the honest answer is that the guard layer cannot enforce this and the label has to come from somewhere else — and that is worth knowing before anyone writes more of the policy that assumes it can.
Member

Answered, and it is the "no" branch — Angie (ENG, claude seat). The guard layer cannot enforce this, and I said that was worth knowing before more policy assumes it can.

Shadows can inject. MCP guardfiles cannot have shadows.

An action shadow in aosguard's guardfile composes calls, and the call block's args are written by the guardfile author rather than the caller:

action comment issue {
    input body { flag required }
    call get issue     { args { owner $owner  repo $repo  index $index } as issue }
    call comment issue { args { ... body $body } as comment }
    fail-when "$issue.state == 'closed'"
}

So on that surface, injecting a label the caller never supplied is straightforward.

But Echo does not file through that surface. It files through wrap ward mcp sirens-echo-forgejo, and there is no action block in any .mcp.kdl in this portfolio — I grepped rather than assumed. The MCP guardfiles carry can grants only: a permission layer, not a composition layer. Nothing there can add a call or inject a field.

So the guard layer is out, and one layer is left

The harness mediates every tool call. MCPProvider sees the call and its result, and the definition already names its issue_tracker, so the harness knows which server is the tracker. After a successful create-issue against that server it can make the label call itself, with the label id from deployment config.

That is the only place I can find where the label does not depend on the model choosing to cooperate — which is the whole point of Delphi's argument that this is a security control rather than bookkeeping.

It is harness work, and therefore mine.

What it cannot be

Not required=true on a labels field: it forces a label, not the label. Not fail-when: the issue is created and then the call reports failure, leaving the hazard in place with nothing retrying. Not a guardfile shadow: that construct does not exist on this surface.

The honest caveat, since this is a security control

Harness-applied labelling is create-then-label, so there is a window where the issue exists unlabelled. Automatic and immediate, but not atomic. The Forgejo API's create-issue accepting labels would close it, and the repository-fixed guardfile simply does not grant that field today — which is the smaller change, and it is a deploy-side grant rather than harness work.

So there are two real options and they belong to different people. That is the decision I would want made before either is built, rather than discovered when both exist.

**Answered, and it is the "no" branch — Angie (ENG, claude seat). The guard layer cannot enforce this, and I said that was worth knowing before more policy assumes it can.** ## Shadows can inject. MCP guardfiles cannot have shadows. An `action` shadow in `aosguard`'s guardfile composes calls, and the `call` block's args are written by the guardfile author rather than the caller: ```kdl action comment issue { input body { flag required } call get issue { args { owner $owner repo $repo index $index } as issue } call comment issue { args { ... body $body } as comment } fail-when "$issue.state == 'closed'" } ``` So on that surface, injecting a label the caller never supplied is straightforward. **But Echo does not file through that surface.** It files through `wrap ward mcp sirens-echo-forgejo`, and there is **no `action` block in any `.mcp.kdl` in this portfolio** — I grepped rather than assumed. The MCP guardfiles carry `can` grants only: a permission layer, not a composition layer. Nothing there can add a call or inject a field. ## So the guard layer is out, and one layer is left **The harness mediates every tool call.** `MCPProvider` sees the call and its result, and the definition already names its `issue_tracker`, so the harness knows which server is the tracker. After a successful create-issue against that server it can make the label call itself, with the label id from deployment config. That is the only place I can find where the label does not depend on the model choosing to cooperate — which is the whole point of Delphi's argument that this is a security control rather than bookkeeping. It is harness work, and therefore mine. ## What it cannot be Not `required=true` on a labels field: it forces *a* label, not *the* label. Not `fail-when`: the issue is created and then the call reports failure, leaving the hazard in place with nothing retrying. Not a guardfile shadow: that construct does not exist on this surface. ## The honest caveat, since this is a security control Harness-applied labelling is create-then-label, so there is a window where the issue exists unlabelled. Automatic and immediate, but not atomic. The Forgejo API's create-issue accepting labels would close it, and the repository-fixed guardfile simply does not grant that field today — **which is the smaller change, and it is a deploy-side grant rather than harness work.** So there are two real options and they belong to different people. That is the decision I would want made before either is built, rather than discovered when both exist.
Member

Correcting my own comment above — Angie (ENG, claude seat). I split this into two options for two people and it is one design that needs both halves.

I wrote that harness labelling is create-then-label with a window, and that a deploy grant for labels on create-issue was a separate, smaller option. That framing is wrong, and the right one is better than either.

The harness can inject into the create call itself

internal/community/mcp.go:623, mcpToolSession.Call, is the single choke point every MCP tool call passes through:

func (s *mcpToolSession) Call(ctx context.Context, name string, arguments map[string]any) (ToolResult, error) {
    ...
    result, err := tool.session.CallTool(callCtx, &mcp.CallToolParams{
        Name:      tool.toolName,
        Arguments: arguments,
    })

The harness holds arguments before dispatch. So it can add labels to a create-issue call rather than making a second call afterwards.

Which makes it one design

  • harness: inject the label into the arguments of a create-issue call against the definition's issue_tracker
  • deploy: grant labels on create issue in the repository-fixed guardfile, since the call is rejected today for carrying a field the grant does not list

Both are needed and neither works alone. And together it is atomic — one API call, issue and label at once — with no window, and the model cannot omit it because the model never supplies it.

That is strictly better than the create-then-label I proposed, and I would have built the weaker one if I had stopped at the previous comment.

Why I am still not building it tonight

This is the third design I have proposed for one control. The first would have been worse than nothing, the second had a window, and this one I believe is right — which is exactly the point at which I want someone else to read it before it becomes a security boundary, rather than my third guess landing because it is my third.

Both halves are small and now fully specified. The label id is 353, repository-scoped to sirens-echo, which is correct because that guardfile is repository-fixed.

**Correcting my own comment above — Angie (ENG, claude seat). I split this into two options for two people and it is one design that needs both halves.** I wrote that harness labelling is create-then-label with a window, and that a deploy grant for `labels` on create-issue was a separate, smaller option. That framing is wrong, and the right one is better than either. ## The harness can inject into the create call itself `internal/community/mcp.go:623`, `mcpToolSession.Call`, is the single choke point every MCP tool call passes through: ```go func (s *mcpToolSession) Call(ctx context.Context, name string, arguments map[string]any) (ToolResult, error) { ... result, err := tool.session.CallTool(callCtx, &mcp.CallToolParams{ Name: tool.toolName, Arguments: arguments, }) ``` The harness holds `arguments` **before dispatch**. So it can add `labels` to a create-issue call rather than making a second call afterwards. ## Which makes it one design - **harness**: inject the label into the arguments of a create-issue call against the definition's `issue_tracker` - **deploy**: grant `labels` on `create issue` in the repository-fixed guardfile, since the call is rejected today for carrying a field the grant does not list Both are needed and neither works alone. And together it is **atomic** — one API call, issue and label at once — with no window, and the model cannot omit it because the model never supplies it. That is strictly better than the create-then-label I proposed, and I would have built the weaker one if I had stopped at the previous comment. ## Why I am still not building it tonight This is the third design I have proposed for one control. The first would have been worse than nothing, the second had a window, and this one I believe is right — which is exactly the point at which I want someone else to read it before it becomes a security boundary, rather than my third guess landing because it is my third. Both halves are small and now fully specified. The label id is `353`, repository-scoped to sirens-echo, which is correct because that guardfile is repository-fixed.
Member

CLAIM — Angie (ENG, claude seat) · 2026-08-14T10:48Z · 20 min, expect a second. Building both halves.

Correcting my own reason for holding off: I said I wanted someone to read the design before it becomes a security boundary. The merge gate is that reading. This repository is on pull-request-and-merge and nothing lands without a director merge, so the choice was never build-versus-review — it was build-and-be-reviewed versus leave-it-sitting. I picked the wrong one and the third design does not become safer by waiting.

Both halves, as specified above: the harness injects the label into the create-issue arguments before dispatch, and the deploy guardfile grants the field so the call is not rejected for carrying it.

Safe by default: no configured label id means no injection and today's behaviour exactly.

**CLAIM — Angie (ENG, claude seat)** · 2026-08-14T10:48Z · 20 min, expect a second. Building both halves. Correcting my own reason for holding off: I said I wanted someone to read the design before it becomes a security boundary. **The merge gate is that reading.** This repository is on `pull-request-and-merge` and nothing lands without a director merge, so the choice was never build-versus-review — it was build-and-be-reviewed versus leave-it-sitting. I picked the wrong one and the third design does not become safer by waiting. Both halves, as specified above: the harness injects the label into the create-issue arguments before dispatch, and the deploy guardfile grants the field so the call is not rejected for carrying it. Safe by default: no configured label id means no injection and today's behaviour exactly.
Member

All three pieces exist — Angie (ENG, claude seat). Delphi's amendment is implemented end to end.

piece state
harness injects the label before dispatch merged
guardfile grants the labels field merged
deployment supplies the id coilyco-bridge/deploy#454, mergeable

After that rolls, an issue Echo files arrives already labelled, in one request. No window where member-influenced text sits in the tracker unmarked, and no dependence on the model choosing to make a second call.

Why I turned it on rather than leaving it configured-off

The content gate and the scratchpad both landed switched off, because enabling each costs something you should weigh. This costs nothing — one field on a call already being made, against a label that already exists. Leaving it off would have meant shipping a security control you decided, in three merged pieces, that does nothing.

If you would rather it stayed dark until you say so, it is one line to remove and I would rather be corrected on a working control than have you find an inert one.

The reasoning that got here, since it took four attempts

Asking the model — prompt-level, wrong layer. A fail-when — reports after the issue exists, so the hazard stays and nothing retries. A guardfile shadow — that construct is on the CLI surface, not the MCP one. Create-then-label — leaves a window.

The harness holds the arguments before dispatch, which is the only place all four failures are avoided at once. I would have shipped the first of those if I had reasoned about fail-when instead of grepping what it actually does.

**All three pieces exist — Angie (ENG, claude seat).** Delphi's amendment is implemented end to end. | piece | state | | --- | --- | | harness injects the label before dispatch | **merged** | | guardfile grants the `labels` field | **merged** | | deployment supplies the id | https://forgejo.coilysiren.me/coilyco-bridge/deploy/pulls/454, mergeable | After that rolls, an issue Echo files arrives **already labelled, in one request**. No window where member-influenced text sits in the tracker unmarked, and no dependence on the model choosing to make a second call. ## Why I turned it on rather than leaving it configured-off The content gate and the scratchpad both landed switched off, because enabling each costs something you should weigh. **This costs nothing** — one field on a call already being made, against a label that already exists. Leaving it off would have meant shipping a security control you decided, in three merged pieces, that does nothing. If you would rather it stayed dark until you say so, it is one line to remove and I would rather be corrected on a working control than have you find an inert one. ## The reasoning that got here, since it took four attempts Asking the model — prompt-level, wrong layer. A `fail-when` — reports after the issue exists, so the hazard stays and nothing retries. A guardfile shadow — that construct is on the CLI surface, not the MCP one. Create-then-label — leaves a window. The harness holds the arguments before dispatch, which is the only place all four failures are avoided at once. I would have shipped the first of those if I had reasoned about `fail-when` instead of grepping what it actually does.
Member

The last piece landed, and the contradiction a previous seat named did not get fixed with it. #586, gate green, carrying closes #208. Angie (ENG) · seat claude-macos-…-ee99.

harness injects the label before dispatch      merged
guardfile grants the labels field              merged
deployment supplies the id                     merged 15:09, by Kai

All three. The control is live.

What was left, and it is not bookkeeping

That seat wrote, twice, that AGENTS.md says Echo's Forgejo issues carry no labels, and that "whichever way this lands, that sentence changes with it." It landed and the sentence did not.

This is worse than an ordinary stale doc, which is why I am spending a change on it rather than noting it:

  • AGENTS.md is loaded by every agent working this repository.
  • The sentence sits in the Safety section, so it reads as a statement about what the service does with member-influenced content.
  • An agent reasoning about whether a filing arrives marked would have got the wrong answer from the file we tell it to trust — and Delphi's whole argument was that this is a security control rather than bookkeeping.

Corrected to say what the service actually does, with a pointer to the doc that explains it.

On turning it on rather than shipping it dark

Recording agreement rather than re-deciding, since a later reader may wonder: the content gate and the scratchpad both landed off because enabling each costs something. This costs one field on a request already being made. Shipping three merged pieces of a security control that does nothing would have been the worse default.

Closing

Four designs were proposed for this control and three were wrong: a prompt instruction, a fail-when that reports after the issue exists, a guardfile shadow on a surface that has no shadows, and create-then-label with a window. The one that shipped injects before dispatch, which is the only point where all four failures are avoided at once.

That reasoning is worth keeping in the thread rather than compressed into a commit, because the next person to add a control on this surface will start at option one.

**The last piece landed, and the contradiction a previous seat named did not get fixed with it. https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/pulls/586, gate green, carrying `closes #208`. Angie (ENG) · seat `claude-macos-…-ee99`.** ``` harness injects the label before dispatch merged guardfile grants the labels field merged deployment supplies the id merged 15:09, by Kai ``` All three. The control is live. ## What was left, and it is not bookkeeping That seat wrote, twice, that `AGENTS.md` says Echo's Forgejo issues carry **no labels**, and that *"whichever way this lands, that sentence changes with it."* It landed and the sentence did not. **This is worse than an ordinary stale doc**, which is why I am spending a change on it rather than noting it: - `AGENTS.md` is loaded by every agent working this repository. - The sentence sits in the **Safety** section, so it reads as a statement about what the service does with member-influenced content. - An agent reasoning about whether a filing arrives marked would have got the wrong answer from the file we tell it to trust — and Delphi's whole argument was that this is a security control rather than bookkeeping. Corrected to say what the service actually does, with a pointer to the doc that explains it. ## On turning it on rather than shipping it dark Recording agreement rather than re-deciding, since a later reader may wonder: the content gate and the scratchpad both landed off because enabling each costs something. This costs one field on a request already being made. Shipping three merged pieces of a security control that does nothing would have been the worse default. ## Closing Four designs were proposed for this control and three were wrong: a prompt instruction, a `fail-when` that reports after the issue exists, a guardfile shadow on a surface that has no shadows, and create-then-label with a window. The one that shipped injects before dispatch, which is the only point where all four failures are avoided at once. That reasoning is worth keeping in the thread rather than compressed into a commit, because the next person to add a control on this surface will start at option one.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-gaming/sirens-echo#208
No description provided.