Skip to content

Bind the node emitter's session into its durable events - #25

Closed
Parad0x-Labs wants to merge 1 commit into
mainfrom
mission/node-emitter-carrier
Closed

Parad0x-Labs wants to merge 1 commit into
mainfrom
mission/node-emitter-carrier

Conversation

@Parad0x-Labs

Copy link
Copy Markdown
Owner

What failed

tests/test_runtime_events_carry_the_client_turn_id.py — 4 of 7 cases red in the main CI
census (run 35570948370) and in every PR run since; failing since the public root
(78f818b), reproducing locally 4/4 deterministic:

  • 3 × AttributeError: 'object' object has no attribute '_emit_runtime_event' at
    core/agent_runtime/agent.py:3681 (raised inside worker threads)
  • test_node_events_carry_the_client_turn_id_through_the_real_runner: 0 events where 4
    were asserted

Root cause (product defect, not just a stale test rig)

Two layers:

  1. The emitter drops its own session. _agent_node_emitter(session_id, source_context)
    documents itself as the per-session durable emitter ("A per-turn emitter putting node
    lifecycle on the durable stream the UI polls") and accepts session_id as its first
    parameter — but the closure never uses it. The streaming seam it routes through
    (_emit_runtime_event → orchestrator.emit_runtime_event →
    core.runtime_task_events.emit_runtime_event) persists only what the source context
    names (runtime_session_id/session_id). A turn whose context carries no session key —
    e.g. the conductor handing the raw ask context — wrote its agent_node_* rows nowhere:
    the blank-Agents-panel regression class this emitter exists to prevent (its own docstring
    cites the 2026-08-29 IDLE-companion incident).
  2. The test rig predates the streaming route. The suite's helper bound the factory with a
    bare object() ("It reads nothing off self" — true when the emitter wrote through a
    module-level append; false since the streaming seam reads self._emit_runtime_event).

Repair

  • core/agent_runtime/agent.py: the emitter captures a sink_context once at creation
    (same concurrency law as the tags it already captures on the turn's thread) with
    setdefault("session_id", session_id) — additive, never overrides a session the caller's
    context already names; the per-emit call routes through the streaming seam with it.
  • tests/test_runtime_events_carry_the_client_turn_id.py: the helper binds the factory to a
    carrier of exactly the two production mixins the streaming route reads
    (ToolResultWorkflowSurfaceMixin for _emit_runtime_event,
    RuntimeCheckpointSupportMixin for the checkpoint resolver) — both stateless adapters, so
    the real emission code still runs without booting an agent. No assertion changed.

Validation

  • Target suite: 4 failed → 7 passed (includes the real-runner case asserting exactly 4
    node events, no duplicates, when the context already names a session — the setdefault
    no-op path).
  • Cumulative neighbors — the twelve suites touching the node-emitter seam
    (test_a_running_node_is_visible_while_it_runs, test_agent_node_events_reach_companion,
    test_node_events_reach_the_pollable_stream, test_chat_companion_laws,
    test_the_agents_panel_shows_work_while_it_happens, test_worker_effect_context_p0, …):
    178 passed, 3 failed — byte-identical result on clean main (the 3 are pre-existing:
    2 × worker_effect served weather + 1 × event_log untagged grouping; stash-verified A/B).
  • ruff check . clean (pinned 0.16.7).

Base: main 8153a96. Head: 3b19e6b. Files: core/agent_runtime/agent.py (+10),
tests/test_runtime_events_carry_the_client_turn_id.py (helper only, assertions unchanged).

_agent_node_emitter takes session_id as its documented per-session
parameter, but the closure never used it: the streaming seam persists
only what the source context names, so a turn whose context carries no
session key (the conductor hands the raw ask context) wrote its node
rows nowhere -- the blank-Agents-panel regression this emitter exists
to prevent. The emitter now captures a sink context once at creation,
defaulting session_id to the session it was built for, additive and
never overriding a session the caller already named.

The client-turn-id suite drove this seam through a bare object(), which
predates the streaming route and died with AttributeError; its emitter
helper now binds the factory to a carrier of exactly the two production
mixins the streaming route reads, so the real emission code still runs
without booting an agent. 4 CI failures (main run 35570948370, every
PR run since) -> 7 passed; the twelve node-event neighbor suites are
identical to clean main.
@Parad0x-Labs
Parad0x-Labs deleted the mission/node-emitter-carrier branch September 23, 2026 03:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant