ADR-0038: One event for both ways of asking¶
Status: Accepted Date: 2026-10-17 Deciders: Alex Nodeland
Context¶
artifactr's forms (artifactr ADR-0074) are asked two ways: ask_form pauses a run, and a form artifact takes submissions. lattice RFC-0005's U2 recommended one event, ui:form_submitted, through relayr's projections, which gained a mapping per deferred tool name (ADR-0018, as amended). A client must not be able to publish one itself. artifactr can't depend on relayr, so the events and projections are relayr's.
Decision¶
- They are
relayr.ui, behind relayr'sformsextra,relayr-ai[forms], which requiresartifactr-ai[forms]. Onlyrelayr.uiimportsartifactr.ui, as the layering test holds, so relayr without the extra is as it was. UiEvent(Event, abstract=True, event_namespace="ui")carriesauthor,thread_idandvalues.FormSubmitted(ui:form_submitted) addsform_idandversionfor a submission, orturn_idandtool_call_idfor an answer, one pair and never both;AppSubmitted(ui:app_submitted) addsapp_idandversion.UI_EVENTSholds both.UI_PROJECTIONSis a typed dict of relayr'sProjectionsarguments,artifacts(submission) anddeferred(ask_form), soProjections(**UI_PROJECTIONS)takes both, and an application merges each with its own of that name. A submission'sartifact_createdbecomes the event; itsartifact_changedandartifact_archivedbecome nothing; an answer replacesartifactr:deferred_answeredfor that tool.- A submission's
thread_idis its command's, the thread it was sent in: a projection is a function of its envelope, and reads nothing else. UI_PUBLISHERS = {"ui": (SourceActor(name="artifactr"),)}, read-only, for reflexr'spublishers=(reflexr ADR-0057): relayr's follower publishes as that actor, and no one else may publish aui:event.
Consequences¶
- Easier: a rule is written once for both ways of asking, and reacts once to each, since relayr keeps the envelope's id.
- Harder: a submission's own artifact events don't reach rules as
artifactr:artifact_*. - Harder: relayr's floors on artifactr,
artifactr-ai>=0.2.0andforms = ["artifactr-ai[forms]>=0.2.0"], name a release withoutDeferredAnswered.tool_nameorartifactr.ui, and can't rise in the change that adds them: uv locks no floor above a workspace member's own version. relayr's next release pull request stays unqueued until artifactr's next release is onmain, and both floors rise to it first, as lattice ADR-0023's amendment holdsartifactr-files-ai's release. A wheel published sooner would install against an artifactr that bridges no answer's tool and has no forms.