From 8f7369bcc83a2bed6a5e892334e7deab857e7b05 Mon Sep 17 00:00:00 2001 From: Adesh Nalpet Adimurthy <390.adesh@gmail.com> Date: Fri, 8 May 2026 13:12:15 -0400 Subject: [PATCH 1/2] Update Custom Event Definition Documentation --- .../docs/development/workflow_agent.md | 94 +++++++++++++++++-- 1 file changed, 84 insertions(+), 10 deletions(-) diff --git a/docs/content/docs/development/workflow_agent.md b/docs/content/docs/development/workflow_agent.md index 5b3da32f2..5d2e9beac 100644 --- a/docs/content/docs/development/workflow_agent.md +++ b/docs/content/docs/development/workflow_agent.md @@ -491,37 +491,111 @@ To use async execution on JDK 21+, user should append jvm option `--add-exports= {{< /tabs >}} ## Event -Events are messages passed between actions. Events may carry payloads. A single event may trigger multiple actions if they are all listening to its type. -There are 2 special types of event. -* `InputEvent`: Generated by the framework, carrying an input data record that arrives at the agent in `input` field . Actions listening to the `InputEvent` will be the entry points of agent. -* `OutputEvent`: The framework will listen to `OutputEvent`, and convert its payload in `output` field into outputs of the agent. By generating `OutputEvent`, actions can emit output data. +Events are JSON-serializable messages passed between actions. Every event has a `type` string used for routing and an `attributes` map that carries the payload. A single event may trigger multiple actions if they are all listening to its type. -User can define own event by extends `Event`. +### Special Events + +* `InputEvent`: Generated by the framework, carrying an input data record that arrives at the agent in its `input` attribute. Actions listening to `InputEvent` are the entry points of the agent. +* `OutputEvent`: The framework listens to `OutputEvent` and converts its `output` attribute into outputs of the agent. + +### Unified Event + +For simple cases, users can pass data between actions directly using `Event` with a custom `type` and `attributes`, without needing to define a subclass. For more structured events, see [Custom Event Subclasses](#custom-event-subclasses) below. + +{{< tabs "Unified Event" >}} + +{{< tab "Python" >}} +```python +# Send a unified event from one action +ctx.send_event(Event(type="my_event", attributes={"field1": "test", "field2": 42})) + +# Consume it in another action +@action("my_event") +@staticmethod +def handle_my_event(event: Event, ctx: RunnerContext) -> None: + field1: str = event.get_attr("field1") + field2: int = event.get_attr("field2") +``` +{{< /tab >}} + +{{< tab "Java" >}} +```java +// Send a unified event from one action +ctx.sendEvent(new Event("my_event", Map.of("field1", "test", "field2", 42))); + +// Consume it in another action +@Action(listenEventTypes = {"my_event"}) +public static void handleMyEvent(Event event, RunnerContext ctx) { + String field1 = (String) event.getAttr("field1"); + int field2 = (int) event.getAttr("field2"); +} +``` +{{< /tab >}} + +{{< /tabs >}} + +### JSON Serialization + +Events are serialized as JSON when passed between Python actions or across the Java-Python boundary. This means attribute values of non-trivial types (such as Pydantic models) lose their type information and arrive as plain `dict` objects. Users must manually reconstruct the typed object: + +```python +input_event = InputEvent.from_event(event) +input_data = ItemData.model_validate(input_event.input) +``` + +### Custom Event Subclasses + +Users can also define custom event subclasses for reusable, structured events. Data should be stored in the `attributes` map, and the subclass must implement a `from_event` / `fromEvent` factory method that validates required attributes and reconstructs typed objects from the deserialized data. {{< tabs "Custom Event" >}} {{< tab "Python" >}} ```python class MyEvent(Event): - value: Any + EVENT_TYPE: ClassVar[str] = "my_event" + + def __init__(self, value: str) -> None: + super().__init__(type=MyEvent.EVENT_TYPE, attributes={"value": value}) + + @classmethod + @override + def from_event(cls, event: Event) -> "MyEvent": + assert "value" in event.attributes + return MyEvent(value=event.attributes["value"]) + + @property + def value(self) -> str: + return self.get_attr("value") ``` {{< /tab >}} {{< tab "Java" >}} ```java public class MyEvent extends Event { - private Object value; + public static final String EVENT_TYPE = "my_event"; + + public MyEvent(String value) { + super(EVENT_TYPE); + setAttr("value", value); + } + + public static MyEvent fromEvent(Event event) { + MyEvent result = new MyEvent((String) event.getAttr("value")); + return result; + } + + public String getValue() { + return (String) getAttr("value"); + } } ``` {{< /tab >}} {{< /tabs >}} -Then, user can define actions listen to or send `MyEvent`. - {{< hint info >}} -The payload of python `Event` should be `BaseModel` serializable, of java `Event` should be json serializable. +All attribute values must be JSON-serializable. In Python, this means `BaseModel`-serializable or primitive types. In Java, values must be Jackson-serializable. {{< /hint >}} ## Built-in Events and Actions From 59bbe8c14abc70b91b0a1cb4df236f23be87dd3a Mon Sep 17 00:00:00 2001 From: Adesh Nalpet Adimurthy <390.adesh@gmail.com> Date: Sat, 9 May 2026 08:40:30 -0400 Subject: [PATCH 2/2] sendEvent from Action for clarity in docs --- docs/content/docs/development/workflow_agent.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/content/docs/development/workflow_agent.md b/docs/content/docs/development/workflow_agent.md index 5d2e9beac..f122727fa 100644 --- a/docs/content/docs/development/workflow_agent.md +++ b/docs/content/docs/development/workflow_agent.md @@ -508,7 +508,12 @@ For simple cases, users can pass data between actions directly using `Event` wit {{< tab "Python" >}} ```python # Send a unified event from one action -ctx.send_event(Event(type="my_event", attributes={"field1": "test", "field2": 42})) +@action(InputEvent.EVENT_TYPE) +@staticmethod +def create_my_event(event: Event, ctx: RunnerContext) -> None: + ctx.send_event( + Event(type="my_event", attributes={"field1": "test", "field2": 42}) + ) # Consume it in another action @action("my_event") @@ -522,7 +527,10 @@ def handle_my_event(event: Event, ctx: RunnerContext) -> None: {{< tab "Java" >}} ```java // Send a unified event from one action -ctx.sendEvent(new Event("my_event", Map.of("field1", "test", "field2", 42))); +@Action(listenEventTypes = {InputEvent.EVENT_TYPE}) +public static void createMyEvent(Event event, RunnerContext ctx) { + ctx.sendEvent(new Event("my_event", Map.of("field1", "test", "field2", 42))); +} // Consume it in another action @Action(listenEventTypes = {"my_event"})