| 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. |