Skip to content

feat(chat): decouple primitives from LangGraph via runtime-neutral ChatAgent contract#135

Merged
blove merged 40 commits into
mainfrom
feat/chat-runtime-decoupling-phase-1
Apr 22, 2026
Merged

feat(chat): decouple primitives from LangGraph via runtime-neutral ChatAgent contract#135
blove merged 40 commits into
mainfrom
feat/chat-runtime-decoupling-phase-1

Conversation

@blove

@blove blove commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Phase-1 of the chat-runtime decoupling: introduces a runtime-neutral `ChatAgent` contract in `@cacheplane/chat` and migrates every primitive and composition off LangGraph's `AgentRef`. Breaks the `chat ↔ langgraph` circular build-graph edge — dependency direction is now strictly one-way (`langgraph → chat`).

  • New contract: `ChatAgent` with signal-based state (`messages`, `status`, `isLoading`, `toolCalls`, `state`) plus optional `interrupt`, `subagents`, and `customEvents$` (Observable). Ships with `mockChatAgent` + `runChatAgentConformance` test helpers.
  • Adapter: `toChatAgent(agentRef)` in `@cacheplane/langgraph` wraps a LangGraph `AgentRef` into a `ChatAgent`, bridging the `customEvents` signal into an RxJS Observable.
  • Primitives migrated (`@cacheplane/chat`): chat-messages, chat-input, chat-tool-calls, chat-typing-indicator, chat-error, chat-interrupt, chat-subagents — all switched from `[ref]` to `[agent]`.
  • Compositions migrated: `` (dropped `langgraphRef` escape hatch, subscribes to `customEvents$` for state updates), chat-interrupt-panel, chat-subagent-card.
  • Relocated to `@cacheplane/langgraph` (per ADR): chat-timeline, chat-timeline-slider, chat-debug and sub-components, mock-agent-ref. These render LangGraph-specific checkpoint/ThreadState UI and don't belong in a runtime-neutral package.
  • Cockpit demos (25 files): updated to bind `[agent]="toChatAgent(stream)"`.

Test Plan

  • `nx test chat` — passes
  • `nx test langgraph` — passes
  • `nx build chat` — passes (production)
  • `nx build langgraph` — passes (production)
  • Nx project graph confirms one-way dep: `langgraph → chat`, no edge back
  • Smoke-test a cockpit chat demo in the browser (reviewer)

🤖 Generated with Claude Code

blove and others added 30 commits April 21, 2026 12:35
Introduces ChatAgent contract (AG-UI-shaped, chat-owned) with LangGraph
and optional AG-UI adapters. Phased delivery; website/docs treated as a
first-class deliverable per phase.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Covers workstreams A (contract) through G (website/docs). Includes
LangGraph adapter, AG-UI adapter scaffold, primitive migrations, package
rename, and documentation alignment.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…d fallback

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… @cacheplane/langgraph

Nx project: agent -> langgraph
npm package: @cacheplane/angular -> @cacheplane/langgraph
Directory:   libs/agent    -> libs/langgraph

Updates tsconfig.base.json path mapping, project.json, ng-package.json,
package.json, CI workflows, all code imports, docs, and website content.
api-docs.json is a generated artifact and will regenerate.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rrowing

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the required `ref: AgentRef` input with `agent: ChatAgent` on the
chat composition. Adds an optional `langgraphRef: AgentRef | undefined` escape
hatch for chat-interrupt and customEvents (phase-1 only) guarded by @if.
Updates onA2uiAction to use ChatSubmitInput.message shape.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…atAgent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds buildCustomEvents$() which uses an effect + cursor to emit only
newly-appended CustomStreamEvent items from the Signal<CustomStreamEvent[]>,
mapping name → type to satisfy the ChatCustomEvent contract. Handles
session resets by detecting when the array length shrinks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
blove and others added 8 commits April 21, 2026 15:22
…scription

Remove the AgentRef escape hatch from ChatComponent: delete the langgraphRef
input and its old customEvents effect that polled ref.customEvents(). Wire
custom events through agent.customEvents$ (Observable) with a one-shot guard
effect in the constructor, and update the interrupt binding to [agent]="agent()".
Guard both constructor effects against NG0950 in Angular 21 zoneless mode.
Add two runInInjectionContext tests that exercise the routing logic and verify
state_update events update the store while non-matching events are ignored.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Swaps SubagentStreamRef from @cacheplane/langgraph to the runtime-neutral
ChatSubagent type from libs/chat/src/lib/agent. Aligns with Phase-1
decoupling objective. Also simplifies status type by importing
ChatSubagentStatus directly.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Replace AgentRef with ChatAgent input; add getInterruptFromAgent helper for
testability. Update specs to test the helper and component definition using
mockChatAgent with withInterrupt option.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…cacheplane/langgraph

Per ADR docs/superpowers/specs/2026-04-21-langgraph-specific-primitives-location.md,
components that render checkpoint_id / ThreadState / fork-replay UI belong in
@cacheplane/langgraph, not in the runtime-neutral @cacheplane/chat.

Files moved (git mv, history preserved):
- libs/chat/src/lib/primitives/chat-timeline/ → libs/langgraph/src/lib/primitives/chat-timeline/
- libs/chat/src/lib/compositions/chat-timeline-slider/ → libs/langgraph/src/lib/compositions/chat-timeline-slider/
- libs/chat/src/lib/compositions/chat-debug/ → libs/langgraph/src/lib/compositions/chat-debug/
- libs/chat/src/lib/testing/mock-agent-ref.{ts,spec.ts} → libs/langgraph/src/lib/testing/

Import fixes in moved files:
- @cacheplane/langgraph self-imports → relative ../agent.types
- Internal chat primitives (ChatMessagesComponent, CHAT_THEME_STYLES, etc.) → @cacheplane/chat
- messageContent() added to @cacheplane/chat public-api to support the peer import

Package.json updates:
- libs/chat/package.json: removed unused @langchain/langgraph-sdk, added missing rxjs
- libs/langgraph/package.json: added @angular/common and @angular/platform-browser

Note: chat→langgraph circular dep in build graph persists until Task 6f drops
@cacheplane/langgraph from libs/chat/package.json peerDependencies.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cular edge

Task 6f of Phase-1 decoupling. After Task 6e relocated LangGraph-specific
primitives to @cacheplane/langgraph, no source in libs/chat imports from
the langgraph package, so the peer-dep can be dropped. Dependency
direction is now strictly one-way: langgraph -> chat.

Also updates the langgraph lint config to accept 'chat' and 'debug'
component-selector prefixes (needed for the moved chat-timeline,
chat-debug, debug-* components to keep their public selectors stable),
and softens a docstring mention of @cacheplane/langgraph in the
ChatAgent contract so @nx/dependency-checks no longer flags it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
All @cacheplane/chat component bindings ([ref]="stream") updated to
[agent]="chatAgent" using toChatAgent(this.stream) direct-call idiom.
Fixes wrong import sources for ChatDebugComponent and
ChatTimelineSliderComponent (moved from @cacheplane/chat to
@cacheplane/langgraph in Phase 6). LangGraph-specific chat-debug and
chat-timeline-slider retain [ref]="stream" bindings throughout.
langgraph/interrupts: migrated submit call to chatAgent.submit({resume:null}).

Affects 25 cockpit components across chat/, langgraph/, and deep-agents/ trees.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Export ChatCustomEvent from @cacheplane/chat public-api (used by
  toChatAgent's signature in @cacheplane/langgraph).
- Import describe/it/expect from vitest in chat-agent-conformance.ts so
  the helper compiles under ng-packagr's production build (previously
  relied on Vitest globals, which aren't in the lib tsconfig types).
- Loosen AgentRef's second type argument in buildCustomEvents$ from
  unknown to any so it satisfies the BagTemplate constraint.
- Wire chat-debug composition (now in @cacheplane/langgraph) to
  toChatAgent for its chat-messages/chat-typing-indicator/chat-error/
  chat-input child bindings, which moved from [ref] to [agent] in
  Phase-1. The AgentRef-shaped [ref] binding is kept for
  chat-debug-summary and chat-debug-controls, which still consume
  AgentRef directly.

With this, `nx build chat`, `nx build langgraph`, `nx test chat`, and
`nx test langgraph` all pass. Dependency direction is strictly one-way:
langgraph -> chat.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@vercel

vercel Bot commented Apr 22, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cacheplane Ready Ready Preview, Comment Apr 22, 2026 0:57am
cacheplane-minting-service Error Error Apr 22, 2026 0:57am

Request Review

blove and others added 2 commits April 21, 2026 17:47
- Drop unused ref inputs on DebugControlsComponent and DebugSummaryComponent
- Consolidate fragmented @cacheplane/chat imports in chat-debug
- Route cockpit messages/input demos through chatAgent.submit

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CI's Library job failed lint on these pre-existing stub methods. The
empty bodies are intentional — they implement the AgentRef surface
for the adapter under test. Scope the disable to the stub helpers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@blove blove merged commit 96cb6d4 into main Apr 22, 2026
14 of 15 checks passed
@blove blove deleted the feat/chat-runtime-decoupling-phase-1 branch May 7, 2026 16:30
blove added a commit that referenced this pull request Jun 9, 2026
…atAgent contract (#135)

* docs(specs): add chat runtime decoupling design

Introduces ChatAgent contract (AG-UI-shaped, chat-owned) with LangGraph
and optional AG-UI adapters. Phased delivery; website/docs treated as a
first-class deliverable per phase.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(specs): define ChatContentBlock in decoupling design

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(plans): add Phase 1 implementation plan for chat runtime decoupling

Covers workstreams A (contract) through G (website/docs). Includes
LangGraph adapter, AG-UI adapter scaffold, primitive migrations, package
rename, and documentation alignment.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(chat): add ChatAgent data types (message, tool call, content block, status)

* feat(chat): add ChatInterrupt, ChatSubagent, submit input/options types

* feat(chat): define ChatAgent runtime-neutral contract

* feat(chat): export ChatAgent contract from public-api

* docs(chat): document runtime-neutral ChatAgent and adapters

* chore(chat): add SPDX header to agent spec files

* docs(chat): clarify ChatSubmitInput.message JSDoc

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(chat): add mockChatAgent() test helper

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(chat): add ChatAgent conformance test suite

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(chat): rename submit param to avoid shadowing in mockChatAgent

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(agent): add toChatAgent() adapter to runtime-neutral ChatAgent contract

* feat(agent): export toChatAgent

* test(agent): verify toChatAgent satisfies ChatAgent conformance

* refactor(agent): rename cryptoRandom to randomId; use for interrupt id fallback

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: rename libs/agent to libs/langgraph; @cacheplane/angular to @cacheplane/langgraph

Nx project: agent -> langgraph
npm package: @cacheplane/angular -> @cacheplane/langgraph
Directory:   libs/agent    -> libs/langgraph

Updates tsconfig.base.json path mapping, project.json, ng-package.json,
package.json, CI workflows, all code imports, docs, and website content.
api-docs.json is a generated artifact and will regenerate.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(chat): migrate chat-input to ChatAgent contract

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(chat): migrate chat-messages to ChatAgent contract

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(chat): migrate chat-tool-calls to ChatAgent contract

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(chat): migrate chat-typing-indicator to ChatAgent contract

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(chat): migrate chat-error to ChatAgent contract

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(chat): drop redundant tool_use cast; rely on discriminant narrowing

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(chat): migrate chat composition core path to ChatAgent

Replaces the required `ref: AgentRef` input with `agent: ChatAgent` on the
chat composition. Adds an optional `langgraphRef: AgentRef | undefined` escape
hatch for chat-interrupt and customEvents (phase-1 only) guarded by @if.
Updates onA2uiAction to use ChatSubmitInput.message shape.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(chat): mark remaining primitives as phase-2/phase-3 migration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(chat): add ChatCustomEvent type and optional customEvents$ to ChatAgent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(chat): extend mock + conformance for customEvents\$

* feat(langgraph): bridge customEvents signal to Observable in toChatAgent

Adds buildCustomEvents$() which uses an effect + cursor to emit only
newly-appended CustomStreamEvent items from the Signal<CustomStreamEvent[]>,
mapping name → type to satisfy the ChatCustomEvent contract. Handles
session resets by detecting when the array length shrinks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(chat): migrate chat-interrupt from AgentRef to ChatAgent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(chat): replace langgraphRef with ChatAgent.customEvents$ subscription

Remove the AgentRef escape hatch from ChatComponent: delete the langgraphRef
input and its old customEvents effect that polled ref.customEvents(). Wire
custom events through agent.customEvents$ (Observable) with a one-shot guard
effect in the constructor, and update the interrupt binding to [agent]="agent()".
Guard both constructor effects against NG0950 in Angular 21 zoneless mode.
Add two runInInjectionContext tests that exercise the routing logic and verify
state_update events update the store while non-matching events are ignored.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(chat): migrate chat-subagents from AgentRef to ChatAgent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(chat): migrate chat-subagent-card to ChatSubagent contract

Swaps SubagentStreamRef from @cacheplane/langgraph to the runtime-neutral
ChatSubagent type from libs/chat/src/lib/agent. Aligns with Phase-1
decoupling objective. Also simplifies status type by importing
ChatSubagentStatus directly.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* refactor(chat): migrate chat-interrupt-panel to ChatAgent contract

Replace AgentRef with ChatAgent input; add getInterruptFromAgent helper for
testability. Update specs to test the helper and component definition using
mockChatAgent with withInterrupt option.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(chat,langgraph): relocate LangGraph-specific primitives to @cacheplane/langgraph

Per ADR docs/superpowers/specs/2026-04-21-langgraph-specific-primitives-location.md,
components that render checkpoint_id / ThreadState / fork-replay UI belong in
@cacheplane/langgraph, not in the runtime-neutral @cacheplane/chat.

Files moved (git mv, history preserved):
- libs/chat/src/lib/primitives/chat-timeline/ → libs/langgraph/src/lib/primitives/chat-timeline/
- libs/chat/src/lib/compositions/chat-timeline-slider/ → libs/langgraph/src/lib/compositions/chat-timeline-slider/
- libs/chat/src/lib/compositions/chat-debug/ → libs/langgraph/src/lib/compositions/chat-debug/
- libs/chat/src/lib/testing/mock-agent-ref.{ts,spec.ts} → libs/langgraph/src/lib/testing/

Import fixes in moved files:
- @cacheplane/langgraph self-imports → relative ../agent.types
- Internal chat primitives (ChatMessagesComponent, CHAT_THEME_STYLES, etc.) → @cacheplane/chat
- messageContent() added to @cacheplane/chat public-api to support the peer import

Package.json updates:
- libs/chat/package.json: removed unused @langchain/langgraph-sdk, added missing rxjs
- libs/langgraph/package.json: added @angular/common and @angular/platform-browser

Note: chat→langgraph circular dep in build graph persists until Task 6f drops
@cacheplane/langgraph from libs/chat/package.json peerDependencies.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(chat): drop @cacheplane/langgraph peer-dep, breaking the circular edge

Task 6f of Phase-1 decoupling. After Task 6e relocated LangGraph-specific
primitives to @cacheplane/langgraph, no source in libs/chat imports from
the langgraph package, so the peer-dep can be dropped. Dependency
direction is now strictly one-way: langgraph -> chat.

Also updates the langgraph lint config to accept 'chat' and 'debug'
component-selector prefixes (needed for the moved chat-timeline,
chat-debug, debug-* components to keep their public selectors stable),
and softens a docstring mention of @cacheplane/langgraph in the
ChatAgent contract so @nx/dependency-checks no longer flags it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(cockpit): bind cockpit demos to ChatAgent via toChatAgent

All @cacheplane/chat component bindings ([ref]="stream") updated to
[agent]="chatAgent" using toChatAgent(this.stream) direct-call idiom.
Fixes wrong import sources for ChatDebugComponent and
ChatTimelineSliderComponent (moved from @cacheplane/chat to
@cacheplane/langgraph in Phase 6). LangGraph-specific chat-debug and
chat-timeline-slider retain [ref]="stream" bindings throughout.
langgraph/interrupts: migrated submit call to chatAgent.submit({resume:null}).

Affects 25 cockpit components across chat/, langgraph/, and deep-agents/ trees.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(chat,langgraph): final Phase-1 build fixes

- Export ChatCustomEvent from @cacheplane/chat public-api (used by
  toChatAgent's signature in @cacheplane/langgraph).
- Import describe/it/expect from vitest in chat-agent-conformance.ts so
  the helper compiles under ng-packagr's production build (previously
  relied on Vitest globals, which aren't in the lib tsconfig types).
- Loosen AgentRef's second type argument in buildCustomEvents$ from
  unknown to any so it satisfies the BagTemplate constraint.
- Wire chat-debug composition (now in @cacheplane/langgraph) to
  toChatAgent for its chat-messages/chat-typing-indicator/chat-error/
  chat-input child bindings, which moved from [ref] to [agent] in
  Phase-1. The AgentRef-shaped [ref] binding is kept for
  chat-debug-summary and chat-debug-controls, which still consume
  AgentRef directly.

With this, `nx build chat`, `nx build langgraph`, `nx test chat`, and
`nx test langgraph` all pass. Dependency direction is strictly one-way:
langgraph -> chat.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(chat,langgraph): polish Phase-1 review nits

- Drop unused ref inputs on DebugControlsComponent and DebugSummaryComponent
- Consolidate fragmented @cacheplane/chat imports in chat-debug
- Route cockpit messages/input demos through chatAgent.submit

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(langgraph): silence no-empty-function on AgentRef test stubs

CI's Library job failed lint on these pre-existing stub methods. The
empty bodies are intentional — they implement the AgentRef surface
for the adapter under test. Scope the disable to the stub helpers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <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