Skip to content

M2-11 Follow-ups with steer and queue

No Date Ruling Reason
D-M2-11-0 2026-10-23 Built on the old integration line (stacked on M2-09 and M2-10) and ported to main after M2-10 landed: only the ticket's own commits were brought over. The migration is renumbered to 00060 behind the migrations main gained meanwhile; changes to the German chapter files of the old base go to their English files on main (09-agents.md, threat model), the rulings and changelog entries to fragments; the txn subscriber messaging.MessageDeleted -> runs follows the order of main as its last entry; generated files were regenerated. The rules of the landing replace the integration branches; main is the reference.
D-M2-11-1 2026-10-21 Placement: follow-ups belong to runs. Pure rules in runs/domain (followup.go, followup_end.go); the service runs/app.Followups joins the unit of work of its caller and is the only code that changes a follow-up state; other modules are reached through their ports (messaging for SetTriggerState and the message facts, chat for the scope, agents for the capability, authz through kernel/authorize). 02 4.2 and 4.3; architecture/modules.yaml lists run_followups, run.followup.create and agent.followup under runs.
D-M2-11-2 2026-10-21 The acceptance locks the active run of the thread (LockActiveRunOfThread) before the decision and evaluates the effective mode once under that lock. A follow-up to a waiting_input run gets agent.followup also with queue or without the capability; a follow-up written with queue while the run was queued or running is not delivered later. 09 7 makes a follow-up to a waiting run the answer, and only the token of a delivery resumes it (R-AG-08); a follow-up that was no answer stays one.
D-M2-11-3 2026-10-21 The adapter's follow-up states move forward only: queued -> delivered, queued -> absorbed, delivered -> absorbed; the same state is no change; every other pair is ErrFollowupTransition = 409 invalid_state_transition and the whole patch is rolled back. The first move out of queued sets trigger_state = followup_delivered; absorbed after delivered changes no trigger state. 06 2 "No effect"; 09 4.7 and 5.3; one message.updated per follow-up keeps R-SY-05.
D-M2-11-4 2026-10-21 agent.followup is deliverable while its row belongs to the run in queued or delivered and the message is not deleted; otherwise Prepare sends nothing without check, token or audit. A denial at delivery sets the row discarded and raises runs.version in the same unit of work as the failed trigger state. A follow-up that will never be processed must not stay open; run_followups has no failed state, trigger_state shows the reason.
D-M2-11-5 2026-10-21 At the end of a run the open follow-ups are locked in ascending message_id; a deleted message is discarded without a trigger state change; after cancelled with user_cancelled the rest are discarded with trigger_state = cancelled, reason user_cancelled; after every other end they become deferred in ascending thread_seq. 04 4 (thread order); 09 6 defines cancelled as "run stopped" with the reason in trigger_state_reason, which shows "not processed, run stopped" (09 7).
D-M2-11-6 2026-10-21 The follow-on run checks each candidate in order with agent.invoke for its author (the re-check principal of M2-08, filter mode at the pinned permission version, no active run), with one audit deny entry of stage run_end per refusal; a refused candidate becomes discarded with failed and the delivery's reason mapping. The first allowed candidate triggers the new run through Open and stays deferred on the ended run; the rest move to the new run as queued with received and get agent.followup after its agent.invocation when the mode asks for it. The follow-on run is an acceptance of the candidate as a trigger (09 3.4 point 7); 03 allows one row per message, so the rest move.
D-M2-11-7 2026-10-21 messaging.MessageDeleted -> runs (Acceptor.OnMessageDeleted, registered after the existing runs subscribers) discards an open follow-up of the deleted message and raises runs.version; the tombstone keeps its trigger state. A deleted triggering message of an active run is not handled here. Deleted content must never become the trigger of a run, and redaction would leave the adapter an empty text (spec gap reported).
D-M2-11-8 2026-10-21 Every server-side change of a follow-up of an active run (acceptance, refused delivery, deletion) raises runs.version by one without run.updated; the end of a run takes no extra step. Focused clients see the follow-up through message.created and message.updated, and the box catches up with the next frame of the run, which the coalescer sends as a snapshot after a version step without patch. 03 runs.version rises with every stored change and the document holds the follow-ups; run.updated may only leave after a commit (M2-10).
D-M2-11-9 2026-10-21 run.followup.create requires All(AtLeast(write), AtLeast(trigger)) with the guard of agent.invoke on the descriptor's Invoke (missing: internal error); the mapping row "write and agent.invoke" replaces the deferred entry, and the golden list of deferred actions no longer names it. 05 4.10; Level(trigger) is what agent.invoke requires, Level(write) keeps the catalogue's wording.
D-M2-11-10 2026-10-21 Every end of a run goes through endRun, which calls Followups.OnRunEnded after the transition: the delivery's denial of a queued invocation and the expiry runs.queued_expiry now end their runs through it as well. The brief asks to route every path that bypasses the extension point of M2-09 through it.
D-M2-11-11 2026-10-21 A follow-on run created without a principal in the unit (a job, a delivery) writes its anchor as the run's agent, the principal of the unit after finishRun. chat refuses a caller other than the principal of the unit; an anonymous unit has none.
D-M2-11-12 2026-10-21 The effective capability is read through agents.Facts.RunFacts, which gains FollowupsPermitted (the owner's supports_followups, nil when absent) and FollowupsReported (the persisted reported_details.capabilities.followups); no new port method. agentstest.ReportCapabilities sets the report in tests. The merged port already reads the agent record per acceptance; the heartbeat of M2-20 persists the report at least every agent.heartbeat_persist_interval.
D-M2-11-13 2026-10-21 messaging.TriggerMessage gains ThreadSeq and Deleted; the end of a run reads the facts of its open follow-ups through TriggerMessageOf. The merged port had no thread position and no tombstone flag.
D-M2-11-14 2026-10-21 The integration tests live next to those of M2-09 in test/integration/runs (deliveries through runs.Delivery.Prepare, the world has no poll) and, for the gateway, next to those of M2-10 in internal/wire. In TestMessageWithoutTriggerIsNoFollowup the owner mentions Texter instead of Alex. The brief allows the place of M2-08 and M2-09; a person mentions only agents of channels the person reads (D-M2-05-22).
D-M2-11-15 2026-10-21 bothy_app gets SELECT, INSERT, UPDATE on run_followups; rows end only through the cascades of their run or message. The server never deletes a follow-up row.