You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Where to fire toolactivated and toolcanceled events? #126
The declarative API explainer introduces the toolactivated and toolcanceled events, leaves the issue of where to fire the events for future discussion. This is that discussion. The explainer initially mentions firing the events at the Window object, however in #76 (comment), @bwalderman mentions that it might make more sense to fire these events at the <form> element itself that is the declarative tool itself.
Initially this makes sense, but if we extend these events to imperative tools, the firing location becomes confusing. For declarative, they'd fire at the element itself, but for imperative they'd fire at Window which feels odd. I think if we had a dedicated Tool interface that inherited from EventTarget to represent imperative tools, then we could have a policy of always firing the events on the tool itself. But absent this, it might make sense to fire all events at the Window and call it a day.
How about makingnavigator.modelContext inherits from EventTarget and have both navigator.modelContext.ontoolactivated and navigator.modelContext.ontoolcanceled attribute handlers?
Having the modelContext be the event target for all tool events has a similar drawback to using the Window as the target: all of your tool events end up in one place, and you may need a big if statement to handle all the different kinds. Tool as a new interface that extends EventTarget is an interesting idea.
If modelContext.registerTool returned a Tool, that could serve as both the event target, and a means of unregistering the tool, which would address some of the concerns about unrelated javascript code unregistering tools you registered (#101)
We did consider that in discussion today. My first thought was that these events are really only useful for declarative tools, and should probably not fire for imperative tools at all. So they should definitely fire on the <form> that backs the tool /cc @mfreed7.
However, I'm not sold that these events are only useful for declarative tools, especially if we decide (as I suspect we will) that developers are responsible for managing concurrency between tools on their own, just as they are responsible for managing concurrency between JavaScript functions that get triggered by (a) other script, (b) user interactions. Designing for that world, then these events are useful for both imperative and declarative, and I vote we mostly follow the approach @beaufortfrancois mentioned above 👍
Give it an element member that points to the form element, if the event represents a declarative tool
Introduce a ToolCanceledEvent with similar properties
This should give developers that need to manage concurrency a good global place to do so, which I suspect is important because concurrency managers might be different from tool authors.
I dumped my thoughts on tool unregistration mechanisms in #130, and after I think I'm convinced that the events should fire on navigator.modelContext, since it's unlikely that tool authors are going to be the same people adding event listeners for these events.
If I'm wrong: (a) we can always reconsider where we fire the events, and (b) there should be enough information on the event I proposed above, to direct the handler to the relevant <form>, or possibly Tool object if we decide to introduce it too. Would love your thoughts.
Looking at #146 , I noticed that toolactivated and toolcanceled events as defined there may not work if multiple concurrent activations of the same tool are allowed. An easy solution here would be to add a unique "activation ID" on the activation events and include that ID with the cancellation event so that the code listening for the event can match them up.
Good call. I like the idea of including a UUID on the event, similar to what the modern Navigation API does with NavigateEvent.destination.id, which holds a UUID. One slightly unfortunate thing is that we'd be storing state directly on the event though, instead of indirectly referencing state on some more stable object; this cuts against what https://w3ctag.github.io/design-principles/#state-and-subclassing recommends, but I don't see a great way around it. Even if we eventually introduce a Tool interface, I don't think that solves this either, since there would be multiple events with different UUIDs pointing at the same Tool.... this probably isn't too bad though?
The upcoming executeTool() could also hypothetically return a ToolExecution EventTarget interface which would contain a state attribute developers could inspect like the following:
I think we can't limit these events to firing on anything associated with the executeTool() API, since tools will mostly be fired outside of that API.
but I don't see a great way around it
[...]
What shall we do then?
I think we proceed as planned—storing the UUID on the event itself. I don't think it's as bad as storing tons of other state and execution details on the event. I think we should plan to do this, but let me also ping @jyasskin from the TAG, to see if he's OK with this.
I don't have a strong opinion here, but some thoughts:
My understanding of declarative tools is that they consist of filling in a form and then submitting it. There's mention of the user reviewing the filled form, and of the submit sometimes causing a navigation. In that context, if an agent tries to run a tool twice concurrently, it's likely to yield inconsistent field values, and one of the tool activations might navigate the page and blow away the other activation's context. So I assume we're talking solely about imperative executions.
Activity
beaufortfrancois commentedon Mar 4, 2026
How about making
navigator.modelContextinherits fromEventTargetand have bothnavigator.modelContext.ontoolactivatedandnavigator.modelContext.ontoolcanceledattribute handlers?bwalderman commentedon Mar 5, 2026
Having the
modelContextbe the event target for all tool events has a similar drawback to using theWindowas the target: all of your tool events end up in one place, and you may need a big if statement to handle all the different kinds.Toolas a new interface that extendsEventTargetis an interesting idea.If
modelContext.registerToolreturned aTool, that could serve as both the event target, and a means of unregistering the tool, which would address some of the concerns about unrelated javascript code unregistering tools you registered (#101)domfarolino commentedon Mar 5, 2026
We did consider that in discussion today. My first thought was that these events are really only useful for declarative tools, and should probably not fire for imperative tools at all. So they should definitely fire on the
<form>that backs the tool /cc @mfreed7.However, I'm not sold that these events are only useful for declarative tools, especially if we decide (as I suspect we will) that developers are responsible for managing concurrency between tools on their own, just as they are responsible for managing concurrency between JavaScript functions that get triggered by (a) other script, (b) user interactions. Designing for that world, then these events are useful for both imperative and declarative, and I vote we mostly follow the approach @beaufortfrancois mentioned above 👍
ModelContextinheritEventTargetToolActivatedEvent(notWebMCPEventlike Chromium has)toolNameattribute, like Chromium haselementmember that points to the form element, if the event represents a declarative toolToolCanceledEventwith similar propertiesThis should give developers that need to manage concurrency a good global place to do so, which I suspect is important because concurrency managers might be different from tool authors.
domfarolino commentedon Mar 5, 2026
Oops sorry, I wrote my comment before @bwalderman's, but didn't hit send or see his until way later!... will review in the context of it...
domfarolino commentedon Mar 5, 2026
I dumped my thoughts on tool unregistration mechanisms in #130, and after I think I'm convinced that the events should fire on
navigator.modelContext, since it's unlikely that tool authors are going to be the same people adding event listeners for these events.If I'm wrong: (a) we can always reconsider where we fire the events, and (b) there should be enough information on the event I proposed above, to direct the handler to the relevant
<form>, or possiblyToolobject if we decide to introduce it too. Would love your thoughts.anssiko commentedon Mar 5, 2026
bwalderman commentedon Mar 19, 2026
Looking at #146 , I noticed that
toolactivatedandtoolcanceledevents as defined there may not work if multiple concurrent activations of the same tool are allowed. An easy solution here would be to add a unique "activation ID" on the activation events and include that ID with the cancellation event so that the code listening for the event can match them up.domfarolino commentedon Mar 20, 2026
Good call. I like the idea of including a UUID on the event, similar to what the modern Navigation API does with
NavigateEvent.destination.id, which holds a UUID. One slightly unfortunate thing is that we'd be storing state directly on the event though, instead of indirectly referencing state on some more stable object; this cuts against what https://w3ctag.github.io/design-principles/#state-and-subclassing recommends, but I don't see a great way around it. Even if we eventually introduce aToolinterface, I don't think that solves this either, since there would be multiple events with different UUIDs pointing at the sameTool.... this probably isn't too bad though?beaufortfrancois commentedon Mar 23, 2026
The upcoming
executeTool()could also hypothetically return aToolExecutionEventTarget interface which would contain a state attribute developers could inspect like the following:beaufortfrancois commentedon Mar 23, 2026
Nevermind, this approach doesn't follow https://w3ctag.github.io/design-principles/#state-and-subclassing.
What shall we do then?
domfarolino commentedon Mar 23, 2026
I think we can't limit these events to firing on anything associated with the
executeTool()API, since tools will mostly be fired outside of that API.[...]
I think we proceed as planned—storing the UUID on the event itself. I don't think it's as bad as storing tons of other state and execution details on the event. I think we should plan to do this, but let me also ping @jyasskin from the TAG, to see if he's OK with this.
jyasskin commentedon Apr 14, 2026
Sorry for my high latency. If you want to check something with the TAG, it may be more efficient to post to https://github.com/w3ctag/design-reviews/issues/new?template=025-question.md so that any member can answer, and so you're more likely to get a consensus opinion.
I don't have a strong opinion here, but some thoughts:
toolactivatedevent and theexecutecall?idattribute.beaufortfrancois commentedon Sep 21, 2026
I think we can close #126 now that #245 has been merged @domfarolino