Why
Streamed lifecycle and text events carry only a task_id. When multiple agents share one task stream (for example, child-workflow sub-agents emitting into a parent orchestrator's stream), their events are indistinguishable at the consumer. The only identity-ish field is author (Literal["user","agent"]), a role, not a source.
What
Thread an optional agent_path (str | list[str]), a single agent id or a root-to-emitter path like ["researcher", "subagent-abc"], end to end:
- Wire:
TaskMessage.agent_path on the envelope, stamped on the in-memory message after messages.create(), so every start/delta/full/done event carries it via parent_task_message. No change to the messages.create/update client contract (stream-only tag).
- Propagation:
ContextInterceptor gains a streaming_agent_path ContextVar plus a context-agent-path header, threaded from workflow to activity independently of the task/trace/span gate (a child workflow can set only _agent_path). This lets the offloaded model activity tag live text and reasoning deltas.
- Emission:
TemporalStreamingHooks takes agent_path and forwards it on tool-request, tool-response, and handoff; stream_lifecycle_content and the streaming service/facade pass it to the envelope; TemporalStreamingModel reads the ContextVar for its three streaming contexts.
Backward compatible
agent_path defaults to None (untagged) everywhere. Existing callers, including subclasses that call super().__init__(task_id=..., timeout=...), are unaffected.
The TaskMessage.agent_path wire field is authored upstream in the backend OpenAPI spec (scaleapi/scale-agentex#381). The edit to src/agentex/types/task_message.py in this PR is a temporary manual patch so the branch is functional and testable now; merging a hand-edited generated type would be wiped on the next Stainless regen.
Merge order:
- Merge scale-agentex#381 (backend schema plus regenerated
openapi.yaml).
- Stainless auto-regenerates this SDK on
next, adding agent_path to the generated types/task_message.py.
- This PR is rebased to drop the manual
types/task_message.py patch, leaving only the lib/ runtime.
Verification
py_compile clean on all touched files; lint (ruff/FA102) fixed.
- Runtime:
TaskMessage.agent_path serializes correctly for str, list[str], and the None default via model_dump(mode="json").
- Deeper Temporal-runtime checks (header round-trip, replay) should run in a bootstrapped env.
Follow-ups (not in this PR)
- Consumers read
parent_task_message.agent_path through a single toPath(x) = Array.isArray(x) ? x : [x] normalizer (for the str | list union).
- Multi-agent consumers with nested sub-agents adopt
agent_path (the parent sets each child's path) and retire any bespoke per-agent attribution workaround.
Generated with Claude Code