Skip to content

Where to fire toolactivated and toolcanceled events? #126

Description

@domfarolino

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.

Thoughts?

Activity

  1. beaufortfrancois commented on Mar 4, 2026

    @beaufortfrancois
    Collaborator

    How about makingnavigator.modelContext inherits from EventTarget and have both navigator.modelContext.ontoolactivated and navigator.modelContext.ontoolcanceled attribute handlers?

  2. bwalderman commented on Mar 5, 2026

    @bwalderman
    Collaborator

    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)

  3. domfarolino commented on Mar 5, 2026

    @domfarolino
    CollaboratorAuthor

    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 👍

    1. Make ModelContext inherit EventTarget
    2. Introduce a ToolActivatedEvent (not WebMCPEvent like Chromium has)
      • Give it a toolName attribute, like Chromium has
      • Give it an element member that points to the form element, if the event represents a declarative tool
    3. 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.

  4. domfarolino commented on Mar 5, 2026

    @domfarolino
    CollaboratorAuthor

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

  5. domfarolino commented on Mar 5, 2026

    @domfarolino
    CollaboratorAuthor

    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.

  6. anssiko commented on Mar 5, 2026

    @anssiko
    Member

    RESOLUTION: The toolactivated and toolcanceled events are fired on the ModelContext object. (issue #126)

  7. bwalderman commented on Mar 19, 2026

    @bwalderman
    Collaborator

    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.

  8. domfarolino commented on Mar 20, 2026

    @domfarolino
    CollaboratorAuthor

    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?

  9. beaufortfrancois commented on Mar 23, 2026

    @beaufortfrancois
    Collaborator

    The upcoming executeTool() could also hypothetically return a ToolExecution EventTarget interface which would contain a state attribute developers could inspect like the following:

    const toolExecution = await navigator.modelContext.executeTool({...});
    console.log(toolExecution.id); // UUID
    
    navigator.modelContext.ontoolactivated = (event) => { console.assert(toolExecution.state === "activated"); } 
    navigator.modelContext.ontoolcancel    = (event) => { console.assert(toolExecution.state === "cancel");    } 
  10. beaufortfrancois commented on Mar 23, 2026

    @beaufortfrancois
    Collaborator

    Nevermind, this approach doesn't follow https://w3ctag.github.io/design-principles/#state-and-subclassing.

    What shall we do then?

  11. domfarolino commented on Mar 23, 2026

    @domfarolino
    CollaboratorAuthor

    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.

  12. jyasskin commented on Apr 14, 2026

    @jyasskin

    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:

    1. 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.
    2. For imperative tools, https://www.w3.org/2026/03/05-webmachinelearning-minutes.html#2afd:~:text=if%20imperative%20tools%20need%20to%20do%20anything%20they%20can%20just%20use%20the%20execute%20function seems right, unless I'm missing a circumstance when there would be actions in between the toolactivated event and the execute call?
    3. This would be easier to understand with a flow chart.
    4. If there's still a need to handle concurrent executions, I wonder if the point in https://w3ctag.github.io/design-principles/#state-and-subclassing about "determine the current state" is relevant. That might argue for exposing an object listing all the active calls, which could be a host for the id attribute.
  13. beaufortfrancois commented on Sep 21, 2026

    @beaufortfrancois
    Collaborator

    I think we can close #126 now that #245 has been merged @domfarolino

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions