Skip to content

feat(c-threads): real SDK-backed threads + inline title generation#481

Merged
blove merged 2 commits into
mainfrom
claude/c-threads-real-titles
May 20, 2026
Merged

feat(c-threads): real SDK-backed threads + inline title generation#481
blove merged 2 commits into
mainfrom
claude/c-threads-real-titles

Conversation

@blove
Copy link
Copy Markdown
Contributor

@blove blove commented May 19, 2026

Summary

  • Backend (cockpit/chat/threads/python/src/graph.py): adds inline generate_title node (Pattern D from spec 2026-05-19-llm-generated-labels-design.md) — same shape that landed in c-a2ui via feat(chat + c-a2ui): LLM-generated labels — thread titles + drop KNOWN_LABELS #474. First turn writes a 3-5 word title to thread.metadata.thread_title; idempotent on subsequent turns.
  • Frontend (cockpit/chat/threads/angular/src/app/threads.component.ts): removes the hardcoded fake-thread array. Constructs a @langchain/langgraph-sdk Client and calls client.threads.search() on init + after every agent turn, mapping metadata.thread_title → Thread.title with a UUID-slice fallback for brand-new threads.

c-threads is the cap that's supposed to demonstrate threads — it now actually does, end-to-end, against the real LangGraph SDK.

Test plan

  • nx build cockpit-chat-threads-angular — green
  • Python graph compiles (from src.graph import graph)
  • Programmatic SDK smoke: "What is the capital of Japan?" → metadata.thread_title = "Capital of Japan"
  • client.threads.search() returns the title for the frontend to consume
  • CI green
  • Manual chrome verification: sidenav shows real LLM-generated titles; switching threads reloads messages

🤖 Generated with Claude Code

Replace c-threads' hardcoded fake thread array with real LangGraph
SDK calls. The threads sidenav now reflects actual server-side
threads with LLM-generated titles instead of static placeholders.

Backend (graph.py): adds inline generate_title node (Pattern D from
spec 2026-05-19-llm-generated-labels-design.md) that writes a 3-5
word title to thread metadata on the first turn. Idempotent and
error-swallowing — title is a UX nicety, never a blocker.

Frontend (threads.component.ts): drops the hardcoded
[{id:'thread-1',...}] array. Constructs a LangGraph SDK Client and
calls client.threads.search() on init + after every agent turn,
mapping metadata.thread_title → Thread.title with a UUID-slice
fallback for brand-new threads.

Verified end-to-end: programmatic SDK smoke confirms
"What is the capital of Japan?" produces
metadata.thread_title = "Capital of Japan", and threads.search
returns it.

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 20, 2026 3:35pm

Request Review

…r pattern

Mirror examples/chat/angular's shell/threads.service.ts wiring: dedicated
injectable ThreadsService that owns the SDK Client and exposes
refresh/create/delete/rename/archive, plus a ThreadActionAdapter on the
component that hooks the framework's right-click menu into the service.

Also fixes a latent bug: passing `apiUrl: '/api'` directly to the
LangGraph SDK Client fails (SDK requires an absolute URL). Apply the
same `window.location.origin + apiUrl` rewrite the streaming transport
uses internally (libs/langgraph/src/lib/transport/fetch-stream.transport.ts).

Refresh trigger switched from agent.isLoading() to agent.status() (the
typed public API), matching demo-shell.component.ts.

The service is a near-copy of the demo's because @ngaf/langgraph does
not yet expose a shared LangGraphThreadsAdapter — see follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@blove blove merged commit a22c9dc into main May 20, 2026
42 checks passed
blove added a commit that referenced this pull request May 20, 2026
The same SDK-backed ThreadsService has been duplicated across consumers
(canonical demo, c-threads cap, future caps). Hoist it into @ngaf/langgraph
so one implementation serves them all.

What's exported:

- createLangGraphClient(apiUrl) — wraps `new Client({ apiUrl })` with
  the absolute-URL rewrite (`/api` → `window.location.origin + /api`)
  the SDK requires. Single source of truth — fetch-stream.transport
  now goes through it instead of inlining the same workaround.

- LangGraphThreadsAdapter — Angular service wrapping
  client.threads.search/create/delete/rename/update with the SDK→Thread
  mapping. Configurable via LANGGRAPH_THREADS_CONFIG which titleMetadataKey
  to read ('title' for the demo, 'thread_title' for c-threads — spec
  2026-05-19-llm-generated-labels-design). Pin/archive/projects/sort
  supported. Failures log to console.error (no more silent catches —
  that's been hiding prod issues; see PR #486).

- refreshOnRunEnd(agent, fn) — collapses the
  `lastStatus = ...; effect(...)` snippet both consumers duplicated for
  "refresh threads when a run finishes" into one line.

Converts examples/chat to consume the adapter directly via
inject(LangGraphThreadsAdapter), drops the local 138-line
shell/threads.service.ts. c-threads' adapter conversion lands in a
follow-up (waiting on #481 merge).

@ngaf/chat stays adapter-agnostic — no new LangChain dependency.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
blove added a commit that referenced this pull request May 20, 2026
PR #481 added a c-threads-local ThreadsService; this drops it in
favor of the shared LangGraphThreadsAdapter (exported from
@ngaf/langgraph in this same PR). Configures the adapter via
LANGGRAPH_THREADS_CONFIG with titleMetadataKey: 'thread_title' to
match the cap's Python graph.

Inline run-end refresh effect collapses to refreshOnRunEnd(agent, fn).

c-threads is now the second consumer of the hoisted adapter — proves
the per-cap titleMetadataKey config works (demo uses 'title',
c-threads uses 'thread_title') without duplicating the SDK glue.

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

* feat(langgraph): hoist threads adapter + Client helper out of demos

The same SDK-backed ThreadsService has been duplicated across consumers
(canonical demo, c-threads cap, future caps). Hoist it into @ngaf/langgraph
so one implementation serves them all.

What's exported:

- createLangGraphClient(apiUrl) — wraps `new Client({ apiUrl })` with
  the absolute-URL rewrite (`/api` → `window.location.origin + /api`)
  the SDK requires. Single source of truth — fetch-stream.transport
  now goes through it instead of inlining the same workaround.

- LangGraphThreadsAdapter — Angular service wrapping
  client.threads.search/create/delete/rename/update with the SDK→Thread
  mapping. Configurable via LANGGRAPH_THREADS_CONFIG which titleMetadataKey
  to read ('title' for the demo, 'thread_title' for c-threads — spec
  2026-05-19-llm-generated-labels-design). Pin/archive/projects/sort
  supported. Failures log to console.error (no more silent catches —
  that's been hiding prod issues; see PR #486).

- refreshOnRunEnd(agent, fn) — collapses the
  `lastStatus = ...; effect(...)` snippet both consumers duplicated for
  "refresh threads when a run finishes" into one line.

Converts examples/chat to consume the adapter directly via
inject(LangGraphThreadsAdapter), drops the local 138-line
shell/threads.service.ts. c-threads' adapter conversion lands in a
follow-up (waiting on #481 merge).

@ngaf/chat stays adapter-agnostic — no new LangChain dependency.

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

* refactor(c-threads): consume shared LangGraphThreadsAdapter

PR #481 added a c-threads-local ThreadsService; this drops it in
favor of the shared LangGraphThreadsAdapter (exported from
@ngaf/langgraph in this same PR). Configures the adapter via
LANGGRAPH_THREADS_CONFIG with titleMetadataKey: 'thread_title' to
match the cap's Python graph.

Inline run-end refresh effect collapses to refreshOnRunEnd(agent, fn).

c-threads is now the second consumer of the hoisted adapter — proves
the per-cap titleMetadataKey config works (demo uses 'title',
c-threads uses 'thread_title') without duplicating the SDK glue.

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

* chore(docs): regenerate api docs

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.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