Skip to content

feat(chat + c-a2ui): LLM-generated labels — thread titles + drop KNOWN_LABELS#474

Merged
blove merged 9 commits into
mainfrom
claude/llm-generated-labels
May 19, 2026
Merged

feat(chat + c-a2ui): LLM-generated labels — thread titles + drop KNOWN_LABELS#474
blove merged 9 commits into
mainfrom
claude/llm-generated-labels

Conversation

@blove
Copy link
Copy Markdown
Contributor

@blove blove commented May 19, 2026

Summary

Implements the LLM-generated labels design (spec 2026-05-19-llm-generated-labels-design.md). Two related fixes in one PR.

1. Action labels — drop hardcoded `KNOWN_LABELS`

PR #464 added a hardcoded map in libs/chat (`bookingSubmit → 'Search flights'`) that embedded app-specific knowledge in the rendering primitive. Removed entirely. Replaced with derivation from the authored UI: when emitting an `A2uiActionMessage`, `buildA2uiActionMessage` walks from the source Button to its child Text and stamps the literal as `action.label`. The transcript renderer (`a2uiActionLabel`) prefers `action.label` and falls back to camelCase humanization. Chat-lib stops knowing about specific app actions.

`deriveActionLabel` accepts both wire forms for the Text `text` field:

  • Canonical wrapped: `{ literalString: "..." }`
  • Raw-string shorthand: `"..."` (what the LLM frequently emits in practice; caught during chrome MCP smoke and fixed in commit 495bf4d)

2. Thread titles — inline LLM node per-cap

Added `generate_title` as a normal LangGraph node in c-a2ui's graph.py (Pattern D from spec — fully inline, ~30 lines, visible in the topology). After the cap's terminal nodes, fires a cheap LLM call (`gpt-5-mini`) summarizing the first user message in 3-5 words and persists via `client.threads.update(thread_id, metadata={'thread_title': ...})`. Idempotent; errors swallowed; never blocks the user-visible response.

Files

  • `libs/a2ui/src/lib/types.ts` — `A2uiActionMessage.action.label?: string`
  • `libs/chat/src/lib/a2ui/build-action-message.ts` — `deriveActionLabel` helper + stamp
  • `libs/chat/src/lib/a2ui/build-action-message.spec.ts` — 5 new cases (incl. raw-string Text shorthand)
  • `libs/chat/src/lib/a2ui/action-label.ts` — drop KNOWN_LABELS; prefer `action.label`
  • `libs/chat/src/lib/a2ui/action-label.spec.ts` — new spec (11 cases)
  • `cockpit/chat/a2ui/python/src/graph.py` — inline `generate_title` node + wiring through terminal nodes

Test plan

  • chat-lib build + tests green (4 spec files; +16 new test cases total)
  • c-a2ui python build + angular build green
  • Shared deploy manifest unchanged
  • Programmatic real-LLM smoke (c-a2ui via SDK): turn 1 writes `thread.metadata.thread_title = 'Fly LAX to JFK'`; turn 2 preserves (idempotency)
  • Chrome MCP end-to-end smoke: clicked 'LAX → JFK' chip → form prefilled (PR fix(c-a2ui): seed booking form origin/dest from prompt to ensure happy path #467 still working) → clicked Search flights → user bubble shows 'Search flights' (derived from authored Button text, NOT from removed KNOWN_LABELS) → thread metadata shows `thread_title: 'Book flight LAX to JFK'` written via SDK
  • CI

Out of scope

c-a2ui only for the inline title node — proves the pattern. Other cockpit caps (c-generative-ui, c-tool-calls, c-subagents, c-interrupts, c-messages, c-input, c-debug, c-theming, c-threads, c-timeline) adopt the same inline pattern in follow-up PRs, one at a time. Frontend SDK adapter mapping `thread.metadata.thread_title → Thread.title` may need verification — backend write is the deliverable for this PR.

Plan: `docs/superpowers/plans/2026-05-19-llm-generated-labels.md`

🤖 Generated with Claude Code

blove and others added 8 commits May 19, 2026 12:37
Two related fixes in one design pass:
- Thread titles via inline per-cap node + LangGraph SDK metadata write
  (Pattern D from the design discussion — fully inline, no shared
  helper, matches per-cap pedagogical purpose).
- Action message labels derived from the authored Button's child Text
  at emit time, killing the hardcoded KNOWN_LABELS map in
  libs/chat/src/lib/a2ui/action-label.ts (PR #464).

Both backed by deep cross-library research (Open Canvas, Vercel,
assistant-ui, CopilotKit, ChatGPT/Claude reference UX) showing
universal consensus: titles are LLM-generated post-first-turn from
thread metadata; action labels come from the authored UI element,
never a centralized map in the rendering primitive.

Scope: c-a2ui first (proves the pattern), document for other caps.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
10-task plan covering:
- libs/a2ui type extension (A2uiActionMessage.action.label?: string)
- libs/chat/a2ui/build-action-message.ts label derivation + 4 new spec cases
- libs/chat/a2ui/action-label.ts: drop KNOWN_LABELS, prefer authored label,
  fall back to camelCase humanizer; 11-case new spec
- cockpit/chat/a2ui/python/src/graph.py: inline generate_title node
  (Pattern D from spec), wired between terminal nodes and END

Implementer = Tasks 1-9 (code, tests, smoke). Orchestrator = Task 10
(push, PR, CI watch, merge).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
threadplane Ready Ready Preview, Comment May 19, 2026 8:12pm

Request Review

@blove blove merged commit 7ac34f9 into main May 19, 2026
2 checks passed
@blove blove deleted the claude/llm-generated-labels branch May 19, 2026 20:13
blove added a commit that referenced this pull request May 20, 2026
PR #474 added a generate_title node to c-a2ui mirroring the
examples/chat pattern — including the same broken localhost:2024
fallback. Same fix: pass `url=None` (via unset env) so the SDK uses
its in-process ASGI transport.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
blove added a commit that referenced this pull request May 20, 2026
…tes (#493)

* fix(graph): use SDK in-process ASGI transport for thread metadata writes

Root cause of the production "all threads Untitled" bug (diagnosed via
PR #492's @Traceable wrapper):

    error_type:    ConnectError
    error_message: All connection attempts failed
    sdk_url:       http://localhost:2024

The Python helper was calling get_client(url='http://localhost:2024')
when LANGGRAPH_API_URL was unset, then trying to HTTP-call back into
the runtime. In local dev this accidentally works because `langgraph
dev` listens on 2024. In prod the runtime is on a different port, so
every title write threw ConnectError and the bare except swallowed it.

Fix: pass `url=os.environ.get("LANGGRAPH_API_URL")` (no fallback). When
None, the SDK uses its in-process ASGI transport — the canonical path
for graph-to-server self-calls. Docstring excerpt:

> If `None`, the client first attempts an in-process connection via
> ASGI transport. ... This only works if the client is used from
> within the Agent server.

Applies to both:
- examples/chat/python (canonical demo, where the bug surfaced)
- cockpit/chat/threads/python (same anti-pattern, would've failed on
  prod for the same reason)

The @Traceable instrumentation from #492 stays — it'll confirm the
fix on the next prod probe by surfacing `wrote_title: <slice>` in
the LangSmith run output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(c-a2ui): same in-process ASGI fix for generate_title node

PR #474 added a generate_title node to c-a2ui mirroring the
examples/chat pattern — including the same broken localhost:2024
fallback. Same fix: pass `url=None` (via unset env) so the SDK uses
its in-process ASGI transport.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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