|
| 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