Skip to content

M2-19 Agent replies and announcements

No Date Ruling Reason
D-M2-19-0 2026-10-22 Built on the old integration line on top of M2-09 and ported to main after M2-09 landed: only the ticket's own commits were brought over; the additions to the German chapter files of the old base go to their English files on main, and the changelog entry and these rulings are fragments. The rules of the landing replace the integration branches; main is the reference.
D-M2-19-1 2026-10-21 The check of run_id is a first branch of the runs subscriber of messaging.MessagePosted (Acceptor.OnMessagePosted, app/answer.go) that vetoes the unit of work, not a new source port; MessagePosted carries RunID. messaging may not import runs (02 4.4), runs already subscribes MessagePosted (02 4.3), and a subscriber may veto (02 4.1 point 4, 8.3).
D-M2-19-2 2026-10-21 A refused run_id answers 400 validation_failed on /run_id with errors[].code run_mismatch and one detail for a person or app, an unknown run, a run of another thread and one of another agent; an ended own run answers 409 conflict with reason run_not_active. Ownership and thread are decided before the state. run_id cannot probe runs outside the authorized thread (05 rule 1.2); run_not_active is the reason M2-10 uses for patches of an ended run.
D-M2-19-3 2026-10-21 No authorize call for the run named in run_id. A run's path is its thread (05 2.2), on which message.create was authorized; runs outside the thread answer like unknown ones.
D-M2-19-4 2026-10-21 createThread refuses first_message.run_id for every principal with 400 validation_failed on /first_message/run_id and run_mismatch, in the body rules before the unit of work. A thread created by the request has no run.
D-M2-19-5 2026-10-21 run_id is validated, carried in MessagePosted and never stored; it is not part of the request hash of client_msg_id. Spec gap: 04 5.1 point 3 does not say whether run_id is part of the canonical request; the core contract is not edited here. messages.run_id names only the live box anchor (03 check (kind = 'run') = (run_id IS NOT NULL)); a replay must work after the run ended (09 15), and a refused answer must be sendable again without run_id.
D-M2-19-6 2026-10-21 The answer check takes the run row FOR SHARE (LockRunForAnswer) after messaging holds the thread row; the ends of a run update it FOR UPDATE. A deadlock with a path that locks run then thread is retried by the unit of work (database.tx_retry_max). Thread then run, like the acceptance of M2-05: every answer stored with run_id commits while the run is active, before its end event (04 10).
D-M2-19-7 2026-10-21 Names and tests that follow the code: the body rules live in messaging/domain (CheckRunID, CheckFirstMessageRunID) and are called from app.prepare and app.CreateThread; the runs integration world of M2-09 (test/integration/runs) builds the brief's world, and it cannot subscribe a test handler (the txn registry needs a handler of an internal package), so the observation of MessagePosted.RunID is in messaging/adapters/httpapi (TestCreateMessage_RunIDCarriedNotStored); delivery in the integration tests uses runs.Delivery like the M2-09 tests instead of pollAgentEvents; the guest run of Texter is triggered by the owner, because a mention resolves only for an author who may read the agent (agent.read, 07 4.6) and Alex reads no channel Texter operates. The brief allows following merged names (D-W-100); each test still proves its rule.