diff --git a/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/architecting-act/SKILL.md b/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/architecting-act/SKILL.md index ee78c15..52c74dd 100644 --- a/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/architecting-act/SKILL.md +++ b/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/architecting-act/SKILL.md @@ -1,7 +1,7 @@ --- name: architecting-act description: Designs Act and Cast architectures through dynamic questioning, outputting validated CLAUDE.md with mermaid diagrams. Covers resilience patterns (Saga/compensation, long-running threads with DeltaChannel, graceful drain checkpoints, node timeout boundaries) from langgraph v1.2+. Use when starting new Act project, adding cast, planning architecture, extracting sub-cast (10+ nodes), redesigning existing cast, or ask "design architecture", "plan cast", "redesign cast", "create CLAUDE.md". -version: "2026.05.26" +version: "2026.05.27" author: Proact0 allowed-tools: - Bash(uv run act cast *) diff --git a/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/developing-cast/SKILL.md b/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/developing-cast/SKILL.md index 14433d7..3885f9e 100644 --- a/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/developing-cast/SKILL.md +++ b/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/developing-cast/SKILL.md @@ -1,7 +1,7 @@ --- name: developing-cast description: Implements LangGraph cast components following systematic workflow (state, deps, nodes, conditions, graph). Use when implementing cast, building nodes/agents/tools, need LangGraph patterns (memory, retry, guardrails, vector stores, node timeouts, error handlers, DeltaChannel, graceful shutdown), or ask "implement cast", "build graph", "add node". -version: "2026.05.26" +version: "2026.05.27" author: Proact0 allowed-tools: - Bash(uv sync *) diff --git a/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/developing-cast/resources/core/state.md b/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/developing-cast/resources/core/state.md index 736bf7e..ac5bb7c 100644 --- a/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/developing-cast/resources/core/state.md +++ b/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/developing-cast/resources/core/state.md @@ -91,9 +91,10 @@ from typing_extensions import TypedDict from langgraph.channels import DeltaChannel -# Bulk reducer: receives state + sequence of all writes from the current step -def list_reducer(state: list[Any], writes: Sequence[list[Any]]) -> list[Any]: - result = list(state) +# Bulk reducer: receives state + sequence of all writes from the current step. +# `state` may be `None` on the very first reconstruction — handle it defensively. +def list_reducer(state: list[Any] | None, writes: Sequence[list[Any]]) -> list[Any]: + result = list(state) if state is not None else [] for write in writes: result.extend(write) return result diff --git a/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/developing-deepagent/SKILL.md b/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/developing-deepagent/SKILL.md index 4b3c163..2e26507 100644 --- a/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/developing-deepagent/SKILL.md +++ b/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/developing-deepagent/SKILL.md @@ -1,7 +1,7 @@ --- name: developing-deepagent description: Implements DeepAgent components using LangChain's deepagents SDK. Use when building deep agents with create_deep_agent, configuring backends/subagents/skills/memory/interpreter, need DeepAgent patterns (sandbox, HITL interrupts, long-term memory, subagent spawning, subagent structured output, QuickJS code interpreter with programmatic tool calling), or ask "implement deep agent", "add subagent", "configure backend", "add interpreter". -version: "2026.05.26" +version: "2026.05.27" author: Proact0 allowed-tools: - Bash(uv sync *) diff --git a/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/streaming-cast/SKILL.md b/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/streaming-cast/SKILL.md index c88ce77..3498ba2 100644 --- a/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/streaming-cast/SKILL.md +++ b/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/streaming-cast/SKILL.md @@ -1,7 +1,7 @@ --- name: streaming-cast description: Implements LangGraph v3 event streaming for graphs with subgraphs and agents. Use when adding streaming to runtime/API endpoint, need token streaming, custom stream projections, subagent streaming, or ask "add streaming", "stream tokens", "stream graph". -version: "2026.05.26" +version: "2026.05.27" author: Proact0 allowed-tools: - Read diff --git a/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/streaming-cast/resources/patterns/integration.md b/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/streaming-cast/resources/patterns/integration.md index d7a83a4..0aae963 100644 --- a/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/streaming-cast/resources/patterns/integration.md +++ b/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/streaming-cast/resources/patterns/integration.md @@ -57,7 +57,10 @@ async def event_generator(query: str, config: dict): "event": "tool_result", "data": { "name": call.tool_name, - "content": str(call.output) if call.error is None else None, + # Pass the raw output — the outer json.dumps handles str/dict/list/None + # natively. Stringifying with str() would produce Python repr like + # "{'k': 'v'}" (single quotes = invalid JSON for the client to re-parse). + "content": call.output if call.error is None else None, "error": str(call.error) if call.error else None, }, }) @@ -158,7 +161,10 @@ async def handle_websocket_message(send_json, data: dict) -> None: await send_json({ "type": "tool_result", "name": call.tool_name, - "content": str(call.output) if call.error is None else None, + # Pass the raw output — send_json's underlying json.dumps handles + # str/dict/list/None natively. Stringifying with str() would produce + # Python repr like "{'k': 'v'}" (single quotes = invalid JSON). + "content": call.output if call.error is None else None, "error": str(call.error) if call.error else None, }) diff --git a/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/streaming-cast/resources/patterns/multiple-modes.md b/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/streaming-cast/resources/patterns/multiple-modes.md index cbe8506..9f35778 100644 --- a/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/streaming-cast/resources/patterns/multiple-modes.md +++ b/act_operator/act_operator/scaffold/{{ cookiecutter.act_slug }}/.claude/skills/streaming-cast/resources/patterns/multiple-modes.md @@ -120,7 +120,7 @@ await asyncio.gather(coordinator_task(), subagent_task()) ## Dispatch Pattern -Clean handler dispatch for multi-projection streams: +Clean handler dispatch for multi-projection streams. Each projection consumer is a wrapper coroutine that iterates its async source and dispatches per item; `asyncio.gather` runs the three consumers concurrently: ```python import asyncio @@ -144,13 +144,23 @@ async def dispatch_subagent(subagent): async for token in message.text: await send({"type": "token", "content": token, "source": subagent.name}) -await asyncio.gather( - *(dispatch_message(m) async for m in stream.messages), - *(dispatch_tool_call(c) async for c in stream.tool_calls), - *(dispatch_subagent(s) async for s in stream.subagents), -) +async def consume_messages(): + async for message in stream.messages: + await dispatch_message(message) + +async def consume_tool_calls(): + async for call in stream.tool_calls: + await dispatch_tool_call(call) + +async def consume_subagents(): + async for subagent in stream.subagents: + await dispatch_subagent(subagent) + +await asyncio.gather(consume_messages(), consume_tool_calls(), consume_subagents()) ``` +> **Why not `*(dispatch_x(x) async for x in stream.x)`?** Async generator expressions cannot be unpacked with `*` (raises `TypeError: 'async_generator' object is not iterable`). Wrapping each projection consumer in its own coroutine preserves streaming semantics: each handler dispatches items as they arrive, instead of buffering the entire stream before `gather` starts. + --- ## Performance