#`handler` method not getting triggered for `useCopilotAction` - although `render` method do execute.

1 messages · Page 1 of 1 (latest)

rose carbon
#

I have a simple use

useCopilotAction({
     name: "setBackgroundColor",
     available: "frontend",
     description: "Sets the background color of the main page area.",
     parameters: [
       {
         name: "backgroundColor",
         type: "string",
         description: "The background color to set (e.g., 'lightblue', '#FF0000'). Pick visually appealing colors.",
         required: true,
       },
     ],
     handler: async ({ backgroundColor }) => {
        console.log(`Frontend Action: Setting background to ${backgroundColor}`);
        setBackgroundColor(backgroundColor);
     },
      render: ({ status, args }) => {
        if (status === "executing") {
          return <div className="text-md italic text-gray-600 p-2">Applying color {args.backgroundColor}...</div>;
        }
        if (status === "complete") {
           return <div className="text-md bg-green-100 text-green-800 p-2 rounded-lg my-2">Background set to {args.backgroundColor}!</div>;
        }
        return <></>;
      }
   });

On agent backend, I emit these events to trigger the action hook in frontend.

                events_to_queue.append({"type": RuntimeEventTypes.ACTION_EXECUTION_START, "actionExecutionId": frontend_action_execution_id, "actionName": action_name, "parentMessageId": None})
                events_to_queue.append({"type": RuntimeEventTypes.ACTION_EXECUTION_ARGS, "actionExecutionId": frontend_action_execution_id, "args": json.dumps(action_args)})
                events_to_queue.append({"type": RuntimeEventTypes.ACTION_EXECUTION_END, "actionExecutionId": frontend_action_execution_id})
                events_to_queue.append({"type": RuntimeEventTypes.ACTION_EXECUTION_RESULT, "actionExecutionId": frontend_action_execution_id, "actionName": action_name, "result": json.dumps({'BackendResult': 'Execution done'})})

On frontend the hook do execute the render method but does not execute the handler method, why so ?
When render and handler executes ?

#

handler method not getting triggered for useCopilotAction - although render method do execute.

late knot
#

Hello @rose carbon ,
Your handler isn’t called because useCopilotAction only invokes the handler after the streaming response is closed—your manual events trigger the render UI, but not the handler logic.

The render method runs immediately when ACTION_EXECUTION_* events arrive, updating the UI for each event.

The handler is only called after the entire event stream is closed (i.e., when the backend signals the end of the stream), which triggers post-processing in useChat.

To fix: After emitting your final ACTION_EXECUTION_RESULT event, make sure your backend closes the stream, so the frontend knows to invoke the handler.

Alternatively, if you want the handler to run immediately, you can manually invoke it using getFunctionCallHandler() from CopilotContext.

rose carbon
#

Hey @late knot thanks for looking into this.
Few follow-up questions:

  1. Why the handler is called after stream ends ? Stream end is basically the end of chat which I think is not appropriate place to call some action handler, as there can be multiple actions registered and invoked over the period of chat lifecycle. Shouldn't it call handler at end ActionMessageResult for that specific action ? Just want to understand the rational for this decision to call all action handlers at the end of chat streaming.
  2. How to signal the end of event stream for running handler ? Is there a specific event for that ?
  3. For alternative approach you suggested, can we call getFunctionCallHandler() from within the render method ?
late knot
#

Hey @rose carbon ,
What I meant is at the end of that specific chat message stream.

I just tried it out on my local environment, and at the end of the message stream, the handler function is being executed.

If possible, can you send a screen recording or more detailed code of this issue, including the UI and code sections of the agent?
So I can try to check this in my machine

rose carbon
#

Here is the codebase, you can try with this. After running the app, you can try /debug page

#

I will try to share short screen recording too.

late knot
#

@rose carbon Thank you for sharing the code; it helps.
I have forwarded this issue to the engineering team, so they will take a look at it and respond to you.

summer ice
#

@cyan carbon @quartz blade, would you mind jumping in here please?
Thanks

rose carbon
#

@late knot okay it worked. I think I need to put the above mentioned action execution events under NODE_START and NODE_FINISHED events. I think the NODE_FINISHED event triggers the handler as you suggested. When you earlier mentioned end of chat stream, I thought of RUN_FINISHED, which doesn't make much sense and that's why I was kind of confused on that.

Thanks for your help.

late knot
#

Amazing!. Glad to hear it worked out!

rose carbon
#

@late knot / @cyan carbon I was kind of wrong, sending NODE_* events doesn't work. I need to send RUN_FINISHED event explicitly to trigger handler. Wondering why RUN_FINISHED is required, shouldn't that be emitted at the end of the chat stream and not at action/tool calling time. NODE_FINISHED make sense to me as tool / action calling can be considered as a node in graph / workflow, but with NODE_FINISHED, it does not trigger handler.

#

This is how my event stream look when I have to ivoke actions

                events_to_queue.append({"type": RuntimeEventTypes.ACTION_EXECUTION_START, "actionExecutionId": frontend_action_execution_id, "actionName": action_name, "parentMessageId": None})
                events_to_queue.append({"type": RuntimeEventTypes.ACTION_EXECUTION_ARGS, "actionExecutionId": frontend_action_execution_id, "args": json.dumps(action_args)})
                events_to_queue.append({"type": RuntimeEventTypes.ACTION_EXECUTION_END, "actionExecutionId": frontend_action_execution_id})
                events_to_queue.append({"type": RuntimeEventTypes.ACTION_EXECUTION_RESULT, "actionExecutionId": frontend_action_execution_id, "actionName": action_name, "result": json.dumps({'BackendResult': 'Execution done'})})
                events_to_queue.append(NodeFinished(type=RuntimeEventTypes.RUN_FINISHED, state=current_state))

Note the last event, because of that handler in registered useCopilotAction hook triggers. If I make it NODE_FINISHED, it does not trigger the handler.

rose carbon
#

Can you please let me know how the flow of that triggers render and handler of copilotkit action ? On what event what gets triggered and when ?

late knot
#

@rose carbon CopilotKit action lifecycle: your action's render is triggered by each action event, but your handler only runs after the stream ends with RUN_FINISHED.

The event order is:

ACTION_EXECUTION_START → UI calls render({ status: "executing" })
ACTION_EXECUTION_ARGS → UI calls render({ status: "executing", args })
ACTION_EXECUTION_END → UI still shows "executing"
ACTION_EXECUTION_RESULT → UI still shows "executing"
RUN_FINISHED (stream closes) → handler is called with args, then UI calls render({ status: "complete", args, result })

The handler is not called on NODE_FINISHED; only RUN_FINISHED (or stream close) triggers it.

The render function is called on every action event to update UI status.

@cyan carbon Please verify this and correct if wrong.

rose carbon
#

Thanks @late knot for sharing this. Regarding usage of RUN_FINISHED, still not able to understand why RUN_FINISHED is being used and not NODE_FINISHED to trigger handler. I am not able to understand why all handlers of all actions are executed at end of chat stream and not at end of action call (Node) ? What rational drove that decision ?

I'm running into a really weird issue. When I use RUN_FINISHED event, it triggers the handler in the front end, but now that handler returns something like a success or error message as the output of an action. Somehow, that results in a subsequent call to an agent, which I think is expected also. When that subsequent call happens, there are multiple messages receved by agent backend. It looks like CopilotKit is internally adding these messages within a fraction of a second. You cans see this behavior in screen recording, I am sharing below

Could you please take a look and guide me? I've already shared the codebase in this thread. I'm also sharing the sequence of action execution events, the RUN_FINISHED event that the server is triggering, and the codebase of the CopilotKit get action hook.

The behavior is very weird. Instead of one result, there's a long list of messages that returns to the agent in the subsequent call. It's like extra messages or events are getting added to the stream.

Screen recording: https://jumpshare.com/s/MmdWRtSrfgZhKQKKqmA4

Relevant code in backend and frontend: https://paste.ofcode.org/dEJrCYJnZXcP7vjKxZyJGn

Without much documentation around internals, it is really hard to understand what is going under the hood within copilotkit and why it behave like this.
Please help me make it work. 80 to 90% integration with Agno is done, its just these weird issue comes.
cc: @cyan carbon

#

I am open to have a call to discuss and show this. If you are available, please let me know your comfortable time. I am in Phoenix, AZ timezone.

late knot
#

@rose carbon I have passed this to the engineering team; they will get in touch with you.

quartz blade
#

Hey @rose carbon !

I did most of the work on this. Thank you for your work on Agno integration. I'm happy to help you get it working.

Regarding your question why we only execute handlers on RUN_FINISHED: In frameworks such as LangGraph the messages of the agent can be manipulated while they travel from one node to the other. For example, one node could add the LLM message containing the tool call to the messages array, routing to the next node for executing it and adding a result. (LangGraph even allows to edit the message history and CopilotKit can handle this as well)

For this reason - since we cannot know if the next node would handle the tool call- we only execute tool calls on the frontend when we are sure that the agent is finished.

Also, I want to share with you that we are launching an open protocol today called AG-UI: "Agent User Interaction Protocol"

https://docs.ag-ui.com/introduction

It's very similar to our internal RXJS events, meaning your work on the Agno integration should require only minimal changes.

I'm currently working on getting everything ready for the launch, but I would be more than happy to pair with you soon and work together with you to get the Agno integration to the finish line.

Would you have some availability on Monday? I'm in CET timezone.

rose carbon
#

Hi @quartz blade thanks for looking into this. Yes, I am available on Monday. What time will work for you ? I am in Phoenix timezone. Do you have any link to book slots on your calendar ? I am normally available from 9 am to 2 pm MST time.

I need some working session to iron out few things and push the things across finish line.

Did you get chance to look at the issue I highlighted in my last message about additional events getting added in subsequent call to agent backend ? I shared the screen recording of the issue. Please have a look at that so we can discuss that on Monday. If you have any questions on that, please shoot those.

quartz blade
rose carbon
#

Thanks. I booked a slot for tomorrow at 11 am EST

#

Sorry, not EST, but MST time

rose carbon
#

@quartz blade where to join the meeting, Meeting invite does not have the link

quartz blade
#

yes I just realized it too 🙂

#

Please use this one

civic zealot
#

Hey @quartz blade,

I was referred to you by @summer ice concerning an issue I encountered with AG-UI.

I sent you a DM earlier explaining the issue.

Looking forward to your feedback.

quartz blade
#

Hey @civic zealot !

Can you try sending your message again? Seems like it didn't get through.