Skip to content

Commit 9064d45

Browse files
bloveclaude
andauthored
feat(website): add 'What injectAgent() Actually Returns' blog post (#836)
* docs: blog sequence + injectAgent post design spec Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: implementation plan for injectAgent blog post Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(website): add 'What injectAgent() Actually Returns' blog post Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(website): tighten claims and example in injectAgent post Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(website): drop licensing callout from injectAgent post Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent fcb23b2 commit 9064d45

3 files changed

Lines changed: 355 additions & 0 deletions

File tree

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
---
2+
title: 'What injectAgent() Actually Returns'
3+
description: 'The signals, the async methods, and the runtime-neutral Agent contract underneath — what you get from one call.'
4+
date: 2026-08-26
5+
tags: [langgraph, angular, signals, agentic-ui]
6+
author: brian
7+
featured: false
8+
draft: false
9+
---
10+
11+
You call `injectAgent()` once, you get one object back — this post is about what's actually in it.
12+
13+
The [API page](/docs/langgraph/api/inject-agent) answers "what's the signature."
14+
That's the right question when you're mid-keystroke.
15+
This post answers the other one: what is each piece of the return value _for_, and why is it shaped the way it is?
16+
17+
## What are the signals?
18+
19+
Six core signals, and together they're the reactive picture most apps need.
20+
21+
Let's take them one at a time:
22+
23+
- `messages` — the conversation as a `Message[]`, updated as tokens stream in. This is what you `@for` over.
24+
- `status` — where the agent is in its run lifecycle, as a single value you can switch on.
25+
- `isLoading``true` while a run is in flight. The signal behind every "Thinking…" indicator.
26+
- `error` — the last run's failure, or `undefined`. Render it; don't `try/catch` your template.
27+
- `toolCalls` — the tool calls the model has made, so you can show work-in-progress instead of dead air.
28+
- `state` — the graph's custom state, typed to your `T` when you use a typed agent ref.
29+
30+
There's a little more on the contract: optional `interrupt` and `subagents` signals when the adapter supports those capabilities, and an `events$` observable for everything that doesn't fit a signal.
31+
32+
That's the surface you bind templates to.
33+
No subscriptions, no `async` pipe bookkeeping, no manual change detection — a streaming token lands in `messages`, and Angular's reactivity does the rest.
34+
35+
## What are the methods?
36+
37+
Four, and they're the imperative half — the things user actions call.
38+
39+
Let's take them in the order you'll reach for them:
40+
41+
- `submit(input, opts?)` — send a user message (or a resume payload, or a state patch) and start a run. Returns a promise that settles when the run does.
42+
- `stop()` — cancel the in-flight run.
43+
- `retry()` — re-run the last submission after a failure. It's deliberately safe to wire to a button: it's a no-op if a run is already in flight or there's nothing to retry.
44+
- `regenerate(assistantMessageIndex)` — discard the assistant message at that index and everything after it, then re-submit the user message that preceded it. This is how "regenerate response" works without you managing message surgery yourself.
45+
46+
Signals tell the template what's true; these methods are how the user changes it.
47+
48+
## Why is the return type two types?
49+
50+
Because most of what you get back isn't LangGraph-shaped — and that's on purpose.
51+
52+
`injectAgent()` from `@threadplane/langgraph` returns a `LangGraphAgent<T>`, which extends the runtime-neutral `Agent` contract (by way of `AgentWithHistory`, which adds a `history` signal — a sub-contract, so don't count on `history` surviving a runtime swap; the AG-UI agent implements plain `Agent`).
53+
Everything above — the six signals, the four methods — lives on that neutral contract.
54+
55+
The neutral slice is what `<chat>` and the other primitives consume.
56+
They don't know they're talking to LangGraph.
57+
58+
Let's look at what sits on top. `LangGraphAgent` adds the runtime-specific members — raw `langGraph*`-prefixed signals that expose the underlying `BaseMessage` and thread-state shapes, plus things like `value`, `branch`/`setBranch`, `switchThread`, and `lifecycle`.
59+
They're additive. You reach for them when you need LangGraph itself; you ignore them when you don't.
60+
61+
Here's the payoff: the AG-UI adapter's `injectAgent()` returns the same neutral slice.
62+
Swap the adapter, and every component bound to `messages`, `isLoading`, and `submit` keeps working.
63+
For me, that's the strongest reason to keep your components on the neutral surface and treat the `langGraph*` members as an escape hatch — [choosing an adapter](/docs/choosing-an-adapter) walks through the tradeoff.
64+
65+
## What does this look like in a component?
66+
67+
Let's put the whole thing in one small component:
68+
69+
```ts
70+
import { Component } from '@angular/core';
71+
import { injectAgent } from '@threadplane/langgraph';
72+
73+
@Component({
74+
selector: 'app-support',
75+
template: `
76+
@for (message of chat.messages(); track message.id) {
77+
<p>{{ message.content }}</p>
78+
}
79+
@if (chat.isLoading()) {
80+
<p>Thinking…</p>
81+
}
82+
`,
83+
})
84+
export class SupportComponent {
85+
readonly chat = injectAgent();
86+
}
87+
```
88+
89+
Sending is the same object: a submit button calls `chat.submit({ message: text })`, and the response streams into `messages` on its own.
90+
This isn't the full setup — `injectAgent()` needs `provideAgent()` configured first, and the [quickstart](/docs/langgraph/getting-started/quickstart) covers that.
91+
92+
## Conclusion
93+
94+
One call returns the whole agent surface: reactive signals for the template, imperative methods for user actions, and a contract that isn't LangGraph-shaped underneath.
95+
That last part is the one I think matters most — bind to the neutral slice and the runtime becomes a swappable detail.
96+
97+
The [API reference](/docs/langgraph/api/inject-agent) has the full signatures, [choosing an adapter](/docs/choosing-an-adapter) covers when the neutral contract earns its keep, and if you haven't built the streaming surface yet, start with [Build a Streaming Chat UI in Angular with LangGraph](/blog/build-a-streaming-chat-ui-in-angular-with-langgraph).
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
# "What `injectAgent()` Actually Returns" Blog Post Implementation Plan
2+
3+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4+
5+
**Goal:** Publish a conceptual blog post targeting the `injectagent` search query (108 impressions, position 5.6) that answers "what is this for," complementing — not duplicating — the API reference page.
6+
7+
**Architecture:** One new MDX file in `apps/website/content/blog/`. No code changes. The post is a "contract tour" in three groups (signals → methods → the two-type return), written in Brian's 2026 technical register per `docs/gtm/voice.md` with the no-anecdotes override.
8+
9+
**Tech Stack:** MDX blog content, Next.js website (`apps/website`), vitest for content validation.
10+
11+
**Spec:** `docs/superpowers/specs/2026-08-26-blog-sequence-inject-agent-design.md`
12+
13+
---
14+
15+
## Verified API facts (source of truth for every claim in the post)
16+
17+
Verified 2026-08-26 against worktree source. **If drafting from a different checkout, re-verify against `libs/chat/src/lib/agent/agent.ts` and `libs/langgraph/src/lib/agent.types.ts` before writing.**
18+
19+
Main can be ahead of npm (releases fire only on a pushed tag). Before finalizing the draft, confirm every member the post names exists in the published `0.0.58` line: `npm pack @threadplane/langgraph@latest @threadplane/chat@latest` into the scratchpad and grep the `.d.ts` for each named signal/method. If a member is main-only, drop it from the post rather than footnoting it.
20+
21+
**Runtime-neutral `Agent<TState>` contract** (`libs/chat/src/lib/agent/agent.ts:27`):
22+
- Signals: `messages` (`Message[]`), `status` (`AgentStatus`), `isLoading` (`boolean`), `error` (`AgentError | undefined`), `toolCalls` (`ToolCall[]`), `state` (`TState`)
23+
- Methods: `submit(input, opts?)`, `stop()`, `retry()`, `regenerate(assistantMessageIndex)`
24+
- Optional: `interrupt?`, `subagents?`, `clientTools?`
25+
- Events: `events$` (Observable, required)
26+
27+
**`AgentWithHistory<TState>`** (`libs/chat/src/lib/agent/agent-with-history.ts:13`) adds `history` (`AgentCheckpoint[]`) and optional `messageCheckpoints`.
28+
29+
**`LangGraphAgent<T>`** (`libs/langgraph/src/lib/agent.types.ts:331`) extends `AgentWithHistory<T>` and adds (selection for the post — do not enumerate all in prose):
30+
- Raw signals prefixed `langGraph*`: `langGraphMessages`, `langGraphInterrupts`, `langGraphToolCalls`, `langGraphHistory` — the prefix exists to avoid collision with the runtime-neutral names
31+
- LangGraph-specific: `value`, `hasValue`, `toolProgress`, `queue`, `branch`/`setBranch`, `isThreadLoading`, `switchThread`, `joinStream`, `activeSubagents`/`getSubagent`/`getSubagentsByType`/`getSubagentsByMessage`, `customEvents`, `lifecycle`, `experimentalBranchTree`, `reload`
32+
- `clientTools` is **required** here (optional on the neutral contract)
33+
- `submit` widens options with `LangGraphSubmitOptions` (resume commands, checkpoint forks)
34+
35+
**Key facts for the "two types" section:**
36+
- `injectAgent()` (no-arg) returns default-typed `LangGraphAgent`; `injectAgent<T>(ref)` with `createAgentRef<T>()` returns `LangGraphAgent<T>` (per `apps/website/content/docs/langgraph/api/inject-agent.mdx`)
37+
- Everything `<chat>` and the other primitives bind lives on the `Agent`/`AgentWithHistory` slice; the LangGraph-specific members are additive
38+
- The AG-UI adapter's `injectAgent()` returns the same neutral slice — that's the swap-runtimes story; link `/docs/choosing-an-adapter`
39+
40+
---
41+
42+
### Task 1: Author the post
43+
44+
**Files:**
45+
- Create: `apps/website/content/blog/2026-08-26-what-inject-agent-returns.mdx`
46+
47+
Slug derives from the filename minus the date prefix (`apps/website/src/lib/blog.ts:34`) → `/blog/what-inject-agent-returns`.
48+
49+
- [ ] **Step 1: Create the file with this exact frontmatter**
50+
51+
```yaml
52+
---
53+
title: 'What injectAgent() Actually Returns'
54+
description: 'The signals, the async methods, and the runtime-neutral Agent contract underneath — what you get from one call.'
55+
date: 2026-08-26
56+
tags: [langgraph, angular, signals, agentic-ui]
57+
author: brian
58+
featured: false
59+
draft: false
60+
---
61+
```
62+
63+
The description is 110 characters — under the 155-char truncation limit in `apps/website/src/lib/docs.ts`. If you edit it, re-count.
64+
65+
- [ ] **Step 2: Write the lede and body sections**
66+
67+
Structure (from the approved spec) with per-section content requirements:
68+
69+
1. **Lede** (no header): one sentence restating the title — one call, one object; here's what's actually in it. Then 2–3 short lines framing the question: the API page answers "what's the signature"; this post answers "what is this for." Link the API page (`/docs/langgraph/api/inject-agent`) in the lede.
70+
2. **`## What are the signals?`** — answer immediately. Name exactly the six core signals from the verified facts (`messages`, `status`, `isLoading`, `error`, `toolCalls`, `state`) and what each is for in one line each. Point: this is the reactive surface you bind templates to; no subscriptions, no manual change detection.
71+
3. **`## What are the methods?`**`submit`, `stop`, `retry`, `regenerate`, each in one or two lines including the non-obvious semantics documented in source (retry is a no-op mid-run; regenerate trims and re-runs from the preceding user message). Point: the imperative surface user actions call.
72+
4. **`## Why is the return type two types?`** — the strategic section. `LangGraphAgent<T>` extends the runtime-neutral `Agent` contract. The neutral slice is what `<chat>` consumes; the `langGraph*`-prefixed signals and LangGraph-specific members (`value`, `branch`, `switchThread`, `lifecycle` — name a handful, don't enumerate all) are additive. The AG-UI adapter returns the same neutral slice, which is what makes runtimes swappable. Link `/docs/choosing-an-adapter`. Flag the recommendation as an opinion ("For me, …" or "I think …").
73+
5. **`## What does this look like in a component?`** — one snippet, verbatim:
74+
75+
```ts
76+
import { Component } from '@angular/core';
77+
import { injectAgent } from '@threadplane/langgraph';
78+
79+
@Component({
80+
selector: 'app-support',
81+
template: `
82+
@for (message of chat.messages(); track message.id) {
83+
<p>{{ message.content }}</p>
84+
}
85+
@if (chat.isLoading()) {
86+
<p>Thinking…</p>
87+
}
88+
`,
89+
})
90+
export class SupportComponent {
91+
readonly chat = injectAgent();
92+
93+
async send(text: string) {
94+
await this.chat.submit({ message: text });
95+
}
96+
}
97+
```
98+
99+
Before using, verify `message.id` and `message.content` exist on `Message` (`libs/chat/src/lib/agent/message.ts`) and that `submit({ message })` matches `AgentSubmitInput` (`libs/chat/src/lib/agent/agent-submit.ts`); adjust the snippet to the real shapes if they differ. Follow with one line: this is not the full setup — link the quickstart (`/docs/langgraph/getting-started/quickstart`) for `provideAgent()` configuration.
100+
6. **`## Conclusion`** — one paragraph restating the takeaway (one call returns the whole agent surface: reactive signals, imperative methods, and a contract that isn't LangGraph-shaped). Forward links: API page, choosing-an-adapter, and the streaming-chat tutorial (`/blog/build-a-streaming-chat-ui-in-angular-with-langgraph`). Close with a forward link or short invitation — no marketing CTA.
101+
102+
Include the standard Threadplane licensing `<Callout>` (copy the exact block from `apps/website/content/blog/2026-08-13-angular-chat-app-tutorial-with-langchain-langgraph.mdx:27-32`) after the lede, since the post shows `@threadplane/chat`-adjacent usage.
103+
104+
- [ ] **Step 3: Voice pass**
105+
106+
Check the draft against `docs/gtm/voice.md` drafting checklist with the 2026 technical override (`docs/gtm/blog-topic-candidates.md` caveats + memory: no invented first-person anecdotes, no emoji, trimmed rhetoric):
107+
108+
- Opens by restating the title; no "Introduction" header
109+
- Contractions present; paragraphs 1–3 lines
110+
- H2-as-question scaffolding, each answered in the first line below it
111+
- At least one "Let's" transition per major section
112+
- Opinions flagged ("I think," "For me")
113+
- No hype vocabulary ("blazing," "game-changing"), no marketing CTA
114+
- Every named signal/method exists in the verified facts above — no inventions
115+
116+
- [ ] **Step 4: Commit**
117+
118+
```bash
119+
git add apps/website/content/blog/2026-08-26-what-inject-agent-returns.mdx
120+
git commit -m "feat(website): add 'What injectAgent() Actually Returns' blog post"
121+
```
122+
123+
---
124+
125+
### Task 2: Validate content and site tests
126+
127+
**Files:**
128+
- No new files; runs existing suites.
129+
130+
- [ ] **Step 1: Verify frontmatter parses and description length**
131+
132+
```bash
133+
cd apps/website && node -e "
134+
const matter = require('gray-matter');
135+
const fs = require('fs');
136+
const f = matter(fs.readFileSync('content/blog/2026-08-26-what-inject-agent-returns.mdx','utf8'));
137+
console.log('desc length:', f.data.description.length);
138+
if (f.data.description.length > 155) throw new Error('description too long');
139+
if (!f.data.title || !f.data.date || f.data.author !== 'brian') throw new Error('frontmatter incomplete');
140+
console.log('OK');
141+
"
142+
```
143+
144+
Expected: `desc length: <n>` (≤155) then `OK`. If `gray-matter` isn't resolvable this way, check how `apps/website/src/lib/blog.ts` imports it and mirror that.
145+
146+
- [ ] **Step 2: Run the website test suite**
147+
148+
`nx test website` does NOT exist (fails silently) — use vitest directly:
149+
150+
```bash
151+
cd apps/website && npx vitest run --config vite.config.mts
152+
```
153+
154+
Expected: all suites pass, including `src/lib/blog.spec.ts` and `src/lib/sitemap-dates.spec.ts`. If a blog spec fails on the new file, fix the post's frontmatter to match what the spec asserts — do not change the spec.
155+
156+
- [ ] **Step 3: Render check in the dev server**
157+
158+
Start the website dev server (use the repo's existing launch config or `npx next dev` from `apps/website`), then load `http://localhost:3000/blog/what-inject-agent-returns` in the browser preview. Verify:
159+
160+
- The post renders (no MDX compile error page)
161+
- The `<Callout>` renders as a styled callout, not raw JSX
162+
- The code block renders with highlighting
163+
- The meta description in `<head>` matches the frontmatter (view source or read_page)
164+
165+
Stop the dev server when done.
166+
167+
- [ ] **Step 4: Commit any fixes**
168+
169+
```bash
170+
git add -A apps/website/content/blog/
171+
git commit -m "fix(website): render fixes for injectAgent post"
172+
```
173+
174+
Skip if Step 3 needed no changes.
175+
176+
---
177+
178+
### Task 3: PR
179+
180+
**Files:**
181+
- None; git/GitHub operations only.
182+
183+
- [ ] **Step 1: Push the branch and open a PR**
184+
185+
```bash
186+
git push -u origin HEAD
187+
gh pr create --title "feat(website): add 'What injectAgent() Actually Returns' blog post" --body "First post of the GSC-driven blog sequence (spec: docs/superpowers/specs/2026-08-26-blog-sequence-inject-agent-design.md).
188+
189+
Targets the \`injectagent\` query — the site's top striking-distance query (108 impressions, position 5.6) — with the conceptual 'what is this for' post; links to (does not replace) the API reference page.
190+
191+
🤖 Generated with [Claude Code](https://claude.com/claude-code)"
192+
```
193+
194+
- [ ] **Step 2: Verify the Vercel preview**
195+
196+
Only `Vercel – threadplane` gates merge. Wait for the preview deployment, open the preview URL's `/blog/what-inject-agent-returns`, and confirm the post renders and appears on `/blog`. Report the preview URL to Brian for final read-through before merge — the post carries his byline, so he approves the prose before it ships.

0 commit comments

Comments
 (0)