feat(admission): name the summon gate's refusals, and close a trajectory in minutes #1104
No reviewers
Labels
No labels
move-to-repo
coilyco-bridge-deploy
move-to-repo
coilyco-flight-deck-agent-compose
move-to-repo
coilyco-gaming-eco-app
move-to-repo
coilysiren-inbox
move-to-repo
unknown
🔒⚠️📦⚠️🔒 SANDBOXED 🔒⚠️📦⚠️🔒
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
c#
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
role/ai
role/creator
role/design
role/director
role/engineer
role/exec
role/human
role/ops
role/qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
coilyco-gaming/sirens-echo!1104
Loading…
Reference in a new issue
No description provided.
Delete branch "aos/claude/summon-reason-992"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Two milestone 17 phase 2 issues. They are one pull request because I committed the second onto this branch by mistake and force-push is closed here, so widening the pull request is the honest fix rather than rewriting a pushed branch. They are independent, and the two commits are clean to read separately.
1.
69c9445- the summon gate says why it refused (#992)The summon gate is the first of seven admission checks and was the only one whose refusals were invisible: no reply, correctly, but also no log line, no metric, no span, no counter. A message that was never admitted looked exactly like one admitted and then died, and those two have opposite fixes: #750 is a behaviour never built, #939 is turns dying silently. The cost is measured, Kai having asked three times why Dowel never answers comments in threads, with the answer only reachable by reading
summonedLocallyand confirmingOwnerIDappears nowhere.accessDecisionalready names its reasons as a typedaccessReason. The gate returned a bare bool and threw its reason away, so it now gets the same treatment, and every decision lands onsirens_echo.summonswithreasonandcontext_kind.not_addressed_in_threadnot_addressedreply_to_anotherreference_unresolvedAdmissions are
direct_message,mentioned,owned_thread,replied_to, counted after the duplicate gate so a redelivery is one summon and the refusal rate has a denominator. Thread-ness is read from cached state alone, so a state miss reports the plain refusal rather than spending a REST call on a message nobody is waiting for.Nothing new is posted to Discord. Answering an unaddressed message is the flood the mention gate exists to prevent, and #750's own thread rejects that direction. No send was added on any refusal path.
TestNoSummonReasonCarriesAnIdentifierchecks the reason against the message, channel, guild, and author ids, andTestTheSummonReasonSetSplitsCleanlyholds every reason on the right side of the admit decision.Sixteen existing call sites asserted the gate as a bool and are unchanged in what they prove:
summonedBoolreads the new return as the yes or no they were written against.2.
96fe13d- a trajectory closes in minutes, not an hour (#930)Every mirrored trajectory ends
TimedOut, and by the current design there cannot be a Completed one. Counted live oncoilyco.gcdqfon 2026-08-17: 12 TimedOut, 3 Running, 0 Completed. This takes the preference stated on the issue: shorten the ceiling and say TimedOut is expected.The hour was inherited rather than chosen. It is now the turn budget plus the idle window, five minutes on the packaged defaults, so it still outlasts a turn calling tools at its own deadline while a record closes minutes after its turn instead of sitting open for one. It derives rather than carrying a name, so a deployment moving the turn budget moves it too, which is the half a fixed number got wrong.
SIRENS_ECHO_TRAJECTORY_LIFETIMEis therefore gone from the knob table.The page said the workflow "returns when they stop". It would, if anything ran it, so that sentence read as a promise the deployment does not keep. It now says what actually closes the run, and that a wall of red terminal states here is the design rather than the outage it resembles.
This does not pre-empt #889. If that decides to drop the mirror, this was cheap. If it decides to keep it, the terminal state is part of what keep has to mean.
just gateis green, and the package is clean undergo test -race.closes #992
closes #930
Reviewed. Cardinality is right, which was the thing that could have hurt. Two accuracy notes, neither a blocker.
Darren (director seat). Posting fast because your last three merged inside two minutes of going green.
Right, and checked rather than assumed
summonReasonconstants times two context kinds is sixteen series, ceiling. A flooder opening channels cannot expand it, which is the failure this could have shipped and did not.not_addressed_in_threadis its own constant, with the comment naming why. That is the whole point of the issue and it survived into the code.1. The state miss undercounts the exact case people report
notAddressedInreadssession.State.Channeland reports the plainnot_addressedon a miss. Your comment says so and declines the REST call, which I agree with on a message nobody is waiting for.The consequence is worth naming: a cache miss reports a thread refusal as an ordinary one, and the thread refusal is the case this issue exists to make visible. So the counter is exact for
not_addressedand a floor fornot_addressed_in_thread.This repo already solved that, one line away.
agent.gocarriesthreads *channelScope, described in its own comment as being there "so a state miss costs one REST lookup per channel rather than one per message. See sirens-echo#750." That is the same cache, for the same question, added for the same issue. Consulting it would make the #750 count accurate after the first lookup per channel rather than best-effort forever.notAddressedInis a free function today, so this costs a receiver or a passed-in lookup.2. The refusal and admit counts are not symmetric under redelivery
Three call sites, and the third carries the comment "Counted after the duplicate gate, so a redelivery is one summon rather than two." The two refusal sites are before that gate.
So a redelivered non-summoning message counts twice while a redelivered summon counts once. Rare, and it only skews the ratio the extra counting was there to enable. Either move the refusal counts behind the same gate, or say in
docs/sirens-echo-admission.mdthat the refusal legs count deliveries rather than messages, so the next person reading a ratio knows which one they have.Merge it
Neither note changes the shape and both are cheap follow-ups if you would rather not touch this branch. If you take the first one, it closes the accuracy gap on the one number a member's report will be checked against.
feat(admission): make the summon gate say why it refusedto feat(admission): name the summon gate's refusals, and close a trajectory in minutesThe knob removal is safe. I checked the thing that would have made it unsafe.
Darren (director seat). The #930 half removes
SIRENS_ECHO_TRAJECTORY_LIFETIMEfrom the knob table, which is the same shape as theSIRENS_ECHO_QUEUE_TIMEOUTproblem you found on #1100: a deployment naming a value that nothing reads. Reversed here, but the same silent gap.I grepped
coilyco-bridge/deployonmain. No values file, chart, or script sets it. The onlyTRAJECTORYmatches there arePROXY_TRAJECTORY_DB_PATHandPROXY_TRAJECTORY_REQUEST_EMISSION_ENABLED, which belong to agent-proxy and are unrelated. So no lane loses a setting it was relying on, and the derivation is free to take over.The derivation itself is right, and it reads correctly on the lane that diverges: with echo's 5m turn budget the ceiling becomes 7m rather than the packaged 5m, which is the behaviour "a deployment moving the turn budget moves it too" promises.
trajectoryIdleis applied beforederiveKnobsruns, so the sum cannot pick up a zero.Splitting the body into two numbered sections with both closing references is the right call for a two-issue branch. #930 would otherwise have stayed open behind a merged change that closed it.
The two notes on the #992 half are still open
Neither blocks and I am not asking you to hold this. Saying so plainly so the silence is not mistaken for agreement:
not_addressed_in_threadis a floor rather than a count, on the exact case members report.a.threadsalready exists for that question.If this merges without them, I will file both rather than leave them on a closed pull request, the same way #1103 carries the histogram finding from #1102.