> ## Documentation Index
> Fetch the complete documentation index at: https://abxbus.archivebox.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Parent-Child Tracking

> Trace nested event flows with automatic parent-child lineage and tree logs.

When a handler emits another event, AbxBus automatically records lineage so you can understand call chains instead of guessing what triggered what.

Repository example files:

* [`examples/parent_child_tracking.py`](https://github.com/ArchiveBox/abxbus/blob/main/examples/parent_child_tracking.py)
* [`abxbus-ts/examples/parent_child_tracking.ts`](https://github.com/ArchiveBox/abxbus/blob/main/abxbus-ts/examples/parent_child_tracking.ts)
* [`abxbus-rust/tests/test_eventbus_dispatch_parent_tracking.rs`](https://github.com/ArchiveBox/abxbus/blob/main/abxbus-rust/tests/test_eventbus_dispatch_parent_tracking.rs)
* [`examples/log_tree_demo.py`](https://github.com/ArchiveBox/abxbus/blob/main/examples/log_tree_demo.py)
* [`abxbus-ts/examples/log_tree_demo.ts`](https://github.com/ArchiveBox/abxbus/blob/main/abxbus-ts/examples/log_tree_demo.ts)

## What gets tracked

* `event_parent_id`: points from child -> parent event
* `event_children`: aggregated list of child events emitted with `event.emit(...)` during handler execution
* `event_emitted_by_handler_id`: which specific handler emitted the child

This tracking works across nested chains (parent -> child -> grandchild) and is surfaced in event helpers and tree logs.

## Which emit style should I use?

Most handler code should use `await event.emit(ChildEvent(...)).now()` in Python/TypeScript, `child.Now()` in Go, or `child.now().await` in Rust. That is the "owned child work" style: the child is linked to the parent, runs immediately from the caller's point of view, and the parent cannot complete until the child completes.

Use the other styles only when you explicitly want linked background work or a fully detached top-level event.

| Style                                           | `event_parent_id` | `event_blocks_parent_completion`                             | Blocks current handler? | Effect                                                                                                                                |
| ----------------------------------------------- | ----------------- | ------------------------------------------------------------ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `await event.emit(ChildEvent(...)).now()`       | Parent event id   | `True` / `true`                                              | Yes                     | Linked child work. The child can queue-jump, and parent completion waits for the child.                                               |
| `event.emit(ChildEvent(...))` without awaiting  | Parent event id   | `False` / `false`                                            | No                      | Linked background child. It appears in ancestry/tree logs, but the parent can complete before the child finishes.                     |
| `await bus.emit(TopLevelEvent(...)).now()`      | `None` / `null`   | `False` / `false` because there is no linked parent to block | Yes                     | Detached top-level event that the current handler waits for naturally because it is awaited. It is not a child in hierarchy tracking. |
| `bus.emit(TopLevelEvent(...))` without awaiting | `None` / `null`   | `False` / `false`                                            | No                      | True background event. It is queued for later processing and has no retained relationship to the event that emitted it.               |

In short: use `event.emit(...)` when you want parent-child lineage, and add `await` when that child work is part of the parent's completion. Use `bus.emit(...)` when the event should be treated as a separate top-level event.

In Rust, the same split is explicit:

* `current_bus.emit_child(ChildEvent { ..., ..Default::default() })` creates linked child work while inside a handler.
* `child.now().await` queue-jumps and waits for that linked child.
* `bus.emit(EventType { ... })` creates detached top-level work.
* calling `now().await` on a bus-emitted event waits for it without adding parent-child lineage.

In Go, the same split is explicit:

* `child := event.Emit(ChildEvent{...})` creates linked child work.
* `child.Now()` queue-jumps and waits for that linked child.
* `bus.Emit(ParentEvent{...})` creates detached top-level work.
* calling `Now()` on a bus-emitted event waits for it without adding parent-child lineage.

## Works across forwarded buses too

Parent-child lineage is preserved even when the parent event has been forwarded between buses.

If a forwarded event is handled on another bus and that handler emits a child:

* the child still gets `event_parent_id = <forwarded parent event_id>`
* the child is linked under the emitting handler's `event_children`
* forwarding that child onward keeps the same lineage metadata

Use `event.emit(...)` in handlers so the runtime can attach ancestry and ownership correctly.

See also: [Forwarding Between Buses](./forwarding-between-buses)

## Queue-jumped vs normally queued linked children

Lineage tracking works in both execution styles:

* Queue-jumped child events:
  * emitted inside a handler and immediately awaited (`await child.now()`)
  * child may execute right away (RPC-style), gets normal parent linkage metadata, and sets `event_blocks_parent_completion=True`
* Normally queued child events:
  * emitted inside a handler but not immediately awaited
  * child runs later via normal queue scheduling, keeps the same `event_parent_id` ancestry link, and leaves `event_blocks_parent_completion=False`

In short: `event.emit(...)` records lineage. Awaiting the emitted event decides whether that lineage also blocks parent completion.

See [Immediate Execution (RPC-style)](../concurrency/immediate-execution) for queue-jump behavior details.

## Full example: checkout -> reserve/charge/receipt (+ fraud grandchild)

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from abxbus import BaseEvent, EventBus

    class CheckoutEvent(BaseEvent[str]):
        order_id: str

    class ReserveInventoryEvent(BaseEvent[str]):
        order_id: str

    class ChargeCardEvent(BaseEvent[str]):
        order_id: str

    class FraudCheckEvent(BaseEvent[str]):
        order_id: str

    class SendReceiptEvent(BaseEvent[str]):
        order_id: str

    bus = EventBus('TreeBus')

    async def on_checkout(event: CheckoutEvent) -> str:
        reserve = event.emit(ReserveInventoryEvent(order_id=event.order_id))
        await reserve.now()
        reserve_id = await reserve.event_result()

        charge = event.emit(ChargeCardEvent(order_id=event.order_id))
        await charge.now()
        charge_id = await charge.event_result()

        receipt = event.emit(SendReceiptEvent(order_id=event.order_id))
        await receipt.now()
        receipt_id = await receipt.event_result()

        return f'{reserve_id}|{charge_id}|{receipt_id}'

    async def on_reserve(event: ReserveInventoryEvent) -> str:
        return f'reserve:{event.order_id}'

    async def on_charge(event: ChargeCardEvent) -> str:
        fraud = event.emit(FraudCheckEvent(order_id=event.order_id))
        await fraud.now()
        fraud_status = await fraud.event_result()
        return f'charge:{event.order_id}:{fraud_status}'

    async def on_fraud(event: FraudCheckEvent) -> str:
        return f'fraud-ok:{event.order_id}'

    async def on_receipt(event: SendReceiptEvent) -> str:
        return f'receipt:{event.order_id}'

    bus.on(CheckoutEvent, on_checkout)
    bus.on(ReserveInventoryEvent, on_reserve)
    bus.on(ChargeCardEvent, on_charge)
    bus.on(FraudCheckEvent, on_fraud)
    bus.on(SendReceiptEvent, on_receipt)

    root = bus.emit(CheckoutEvent(order_id='ord-123'))
    result = await root.event_result()
    await bus.wait_until_idle()

    print(result)
    print(bus.log_tree())
    ```
  </Tab>

  <Tab title="TypeScript">
    ```ts theme={null}
    import { BaseEvent, EventBus } from 'abxbus'
    import { z } from 'zod'

    const CheckoutEvent = BaseEvent.extend('CheckoutEvent', {
      order_id: z.string(),
      event_result_type: z.string(),
    })
    const ReserveInventoryEvent = BaseEvent.extend('ReserveInventoryEvent', {
      order_id: z.string(),
      event_result_type: z.string(),
    })
    const ChargeCardEvent = BaseEvent.extend('ChargeCardEvent', {
      order_id: z.string(),
      event_result_type: z.string(),
    })
    const FraudCheckEvent = BaseEvent.extend('FraudCheckEvent', {
      order_id: z.string(),
      event_result_type: z.string(),
    })
    const SendReceiptEvent = BaseEvent.extend('SendReceiptEvent', {
      order_id: z.string(),
      event_result_type: z.string(),
    })

    const bus = new EventBus('TreeBus')

    bus.on(CheckoutEvent, async (event) => {
      const reserve = event.emit(ReserveInventoryEvent({ order_id: event.order_id }))
      await reserve.now()

      const charge = event.emit(ChargeCardEvent({ order_id: event.order_id }))
      await charge.now()

      const receipt = event.emit(SendReceiptEvent({ order_id: event.order_id }))
      await receipt.now()

      return `${await reserve.eventResult()}|${await charge.eventResult()}|${await receipt.eventResult()}`
    })

    bus.on(ReserveInventoryEvent, async (event) => `reserve:${event.order_id}`)
    bus.on(ChargeCardEvent, async (event) => {
      const fraud = event.emit(FraudCheckEvent({ order_id: event.order_id }))
      await fraud.now()
      return `charge:${event.order_id}:${await fraud.eventResult()}`
    })

    bus.on(FraudCheckEvent, async (event) => `fraud-ok:${event.order_id}`)
    bus.on(SendReceiptEvent, async (event) => `receipt:${event.order_id}`)

    const root = bus.emit(CheckoutEvent({ order_id: 'ord-123' }))
    await root.now()
    await bus.waitUntilIdle()

    console.log(await root.eventResult())
    console.log(bus.logTree())
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    use abxbus::{event, event_bus::EventBus};
    use futures::executor::block_on;
    use serde_json::json;

    event! {
        struct CheckoutEvent {
            order_id: String,
            event_result_type: serde_json::Value,
        }
    }

    event! {
        struct ReserveInventoryEvent {
            order_id: String,
            event_result_type: serde_json::Value,
        }
    }

    event! {
        struct SendReceiptEvent {
            order_id: String,
            event_result_type: serde_json::Value,
        }
    }

    let bus = EventBus::new(Some("TreeBus".to_string()));

    bus.on(CheckoutEvent, |event: CheckoutEvent| async move {
        let current_bus = event.event_bus().expect("handler bus");
        let order_id = event.order_id;

        let reserve = current_bus.emit_child(ReserveInventoryEvent {
            order_id: order_id.clone(),
            ..Default::default()
        });
        reserve.now().await;
        let reserve_id = reserve.event_result().await?;

        let receipt = current_bus.emit_child(SendReceiptEvent {
            order_id,
            ..Default::default()
        });
        receipt.now().await;
        let receipt_id = receipt.event_result().await?;

        Ok(json!(format!("{reserve_id}|{receipt_id}")))
    });

    bus.on(ReserveInventoryEvent, |event: ReserveInventoryEvent| async move {
        Ok(json!(format!("reserve:{}", event.order_id)))
    });
    bus.on(SendReceiptEvent, |event: SendReceiptEvent| async move {
        Ok(json!(format!("receipt:{}", event.order_id)))
    });

    let root = bus.emit(CheckoutEvent {
        order_id: "ord-123".to_string(),
        ..Default::default()
    });
    let result = block_on(root.event_result())?;
    block_on(bus.wait_until_idle(None));

    println!("{result}");
    println!("{}", bus.log_tree());
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    package main

    import (
    	"fmt"

    	abxbus "github.com/ArchiveBox/abxbus/abxbus-go"
    )

    func main() {
    	bus := abxbus.NewEventBus("TreeBus", nil)

    	bus.On("CheckoutEvent", "on_checkout", func(event *abxbus.BaseEvent) (any, error) {
    		orderID := event.Payload["order_id"].(string)

    		reserve := event.Emit(abxbus.NewBaseEvent("ReserveInventoryEvent", map[string]any{"order_id": orderID}))
    		if _, err := reserve.Now(); err != nil {
    			return nil, err
    		}
    		reserveID, err := reserve.EventResult()
    		if err != nil {
    			return nil, err
    		}

    		charge := event.Emit(abxbus.NewBaseEvent("ChargeCardEvent", map[string]any{"order_id": orderID}))
    		if _, err := charge.Now(); err != nil {
    			return nil, err
    		}
    		chargeID, err := charge.EventResult()
    		if err != nil {
    			return nil, err
    		}

    		receipt := event.Emit(abxbus.NewBaseEvent("SendReceiptEvent", map[string]any{"order_id": orderID}))
    		if _, err := receipt.Now(); err != nil {
    			return nil, err
    		}
    		receiptID, err := receipt.EventResult()
    		if err != nil {
    			return nil, err
    		}

    		return fmt.Sprintf("%v|%v|%v", reserveID, chargeID, receiptID), nil
    	}, nil)

    	bus.On("ReserveInventoryEvent", "on_reserve", func(event *abxbus.BaseEvent) (any, error) {
    		return fmt.Sprintf("reserve:%s", event.Payload["order_id"]), nil
    	}, nil)

    	bus.On("ChargeCardEvent", "on_charge", func(event *abxbus.BaseEvent) (any, error) {
    		orderID := event.Payload["order_id"].(string)
    		fraud := event.Emit(abxbus.NewBaseEvent("FraudCheckEvent", map[string]any{"order_id": orderID}))
    		if _, err := fraud.Now(); err != nil {
    			return nil, err
    		}
    		fraudStatus, err := fraud.EventResult()
    		if err != nil {
    			return nil, err
    		}
    		return fmt.Sprintf("charge:%s:%v", orderID, fraudStatus), nil
    	}, nil)

    	bus.On("FraudCheckEvent", "on_fraud", func(event *abxbus.BaseEvent) (any, error) {
    		return fmt.Sprintf("fraud-ok:%s", event.Payload["order_id"]), nil
    	}, nil)
    	bus.On("SendReceiptEvent", "on_receipt", func(event *abxbus.BaseEvent) (any, error) {
    		return fmt.Sprintf("receipt:%s", event.Payload["order_id"]), nil
    	}, nil)

    	root := bus.Emit(abxbus.NewBaseEvent("CheckoutEvent", map[string]any{"order_id": "ord-123"}))
    	result, err := root.EventResult()
    	if err != nil {
    		panic(err)
    	}
    	bus.WaitUntilIdle(nil)

    	fmt.Println(result)
    	fmt.Println(bus.LogTree())
    }
    ```
  </Tab>
</Tabs>

## Example tree output

Captured from running the Python example above with `uv run` (IDs/timestamps vary run-to-run):

```text theme={null}
└── CheckoutEvent#b7c7 [10:10:54.522 (0.003s)]
    └── ✅ TreeBus#ef2a.__main__.on_checkout#7a12 [10:10:54.522 (0.002s)] → 'reserve:ord-123|charge:ord-123:fraud-ok:ord-123|receipt:ord-123'
        ├── ReserveInventoryEvent#ca2f [10:10:54.522 (0.000s)]
        │   └── ✅ TreeBus#ef2a.__main__.on_reserve#1583 [10:10:54.522 (0.000s)] → 'reserve:ord-123'
        ├── ChargeCardEvent#b746 [10:10:54.523 (0.001s)]
        │   └── ✅ TreeBus#ef2a.__main__.on_charge#7d9c [10:10:54.523 (0.001s)] → 'charge:ord-123:fraud-ok:ord-123'
        │       └── FraudCheckEvent#31e0 [10:10:54.523 (0.000s)]
        │           └── ✅ TreeBus#ef2a.__main__.on_fraud#4c4e [10:10:54.523 (0.000s)] → 'fraud-ok:ord-123'
        └── SendReceiptEvent#c399 [10:10:54.524 (0.000s)]
            └── ✅ TreeBus#ef2a.__main__.on_receipt#de9f [10:10:54.524 (0.000s)] → 'receipt:ord-123'
```

## Why this helps in practice

* Debugging: quickly see causality chains instead of inspecting raw logs line-by-line.
* Reliability: timeout/cancellation behavior can be reasoned about by ancestry.
* Querying: combine lineage with `find(..., child_of=...)` to isolate event families.

## Related pages

* [Immediate Execution (RPC-style)](../concurrency/immediate-execution)
* [Forwarding Between Buses](./forwarding-between-buses)
* [OtelTracingMiddleware](../integrations/middleware-otel-tracing)
* [Find Events](./find-events)
* [BaseEvent](../api/baseevent)
