diff --git a/apps/website/content/blog/2026-08-26-what-inject-agent-returns.mdx b/apps/website/content/blog/2026-08-26-what-inject-agent-returns.mdx new file mode 100644 index 000000000..08c2bde31 --- /dev/null +++ b/apps/website/content/blog/2026-08-26-what-inject-agent-returns.mdx @@ -0,0 +1,97 @@ +--- +title: 'What injectAgent() Actually Returns' +description: 'The signals, the async methods, and the runtime-neutral Agent contract underneath — what you get from one call.' +date: 2026-08-26 +tags: [langgraph, angular, signals, agentic-ui] +author: brian +featured: false +draft: false +--- + +You call `injectAgent()` once, you get one object back — this post is about what's actually in it. + +The [API page](/docs/langgraph/api/inject-agent) answers "what's the signature." +That's the right question when you're mid-keystroke. +This post answers the other one: what is each piece of the return value _for_, and why is it shaped the way it is? + +## What are the signals? + +Six core signals, and together they're the reactive picture most apps need. + +Let's take them one at a time: + +- `messages` — the conversation as a `Message[]`, updated as tokens stream in. This is what you `@for` over. +- `status` — where the agent is in its run lifecycle, as a single value you can switch on. +- `isLoading` — `true` while a run is in flight. The signal behind every "Thinking…" indicator. +- `error` — the last run's failure, or `undefined`. Render it; don't `try/catch` your template. +- `toolCalls` — the tool calls the model has made, so you can show work-in-progress instead of dead air. +- `state` — the graph's custom state, typed to your `T` when you use a typed agent ref. + +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. + +That's the surface you bind templates to. +No subscriptions, no `async` pipe bookkeeping, no manual change detection — a streaming token lands in `messages`, and Angular's reactivity does the rest. + +## What are the methods? + +Four, and they're the imperative half — the things user actions call. + +Let's take them in the order you'll reach for them: + +- `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. +- `stop()` — cancel the in-flight run. +- `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. +- `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. + +Signals tell the template what's true; these methods are how the user changes it. + +## Why is the return type two types? + +Because most of what you get back isn't LangGraph-shaped — and that's on purpose. + +`injectAgent()` from `@threadplane/langgraph` returns a `LangGraphAgent`, 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`). +Everything above — the six signals, the four methods — lives on that neutral contract. + +The neutral slice is what `` and the other primitives consume. +They don't know they're talking to LangGraph. + +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`. +They're additive. You reach for them when you need LangGraph itself; you ignore them when you don't. + +Here's the payoff: the AG-UI adapter's `injectAgent()` returns the same neutral slice. +Swap the adapter, and every component bound to `messages`, `isLoading`, and `submit` keeps working. +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. + +## What does this look like in a component? + +Let's put the whole thing in one small component: + +```ts +import { Component } from '@angular/core'; +import { injectAgent } from '@threadplane/langgraph'; + +@Component({ + selector: 'app-support', + template: ` + @for (message of chat.messages(); track message.id) { +

{{ message.content }}

+ } + @if (chat.isLoading()) { +

Thinking…

+ } + `, +}) +export class SupportComponent { + readonly chat = injectAgent(); +} +``` + +Sending is the same object: a submit button calls `chat.submit({ message: text })`, and the response streams into `messages` on its own. +This isn't the full setup — `injectAgent()` needs `provideAgent()` configured first, and the [quickstart](/docs/langgraph/getting-started/quickstart) covers that. + +## Conclusion + +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. +That last part is the one I think matters most — bind to the neutral slice and the runtime becomes a swappable detail. + +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). diff --git a/docs/superpowers/plans/2026-08-26-what-inject-agent-returns-post.md b/docs/superpowers/plans/2026-08-26-what-inject-agent-returns-post.md new file mode 100644 index 000000000..12623ac03 --- /dev/null +++ b/docs/superpowers/plans/2026-08-26-what-inject-agent-returns-post.md @@ -0,0 +1,196 @@ +# "What `injectAgent()` Actually Returns" Blog Post Implementation Plan + +> **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. + +**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. + +**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. + +**Tech Stack:** MDX blog content, Next.js website (`apps/website`), vitest for content validation. + +**Spec:** `docs/superpowers/specs/2026-08-26-blog-sequence-inject-agent-design.md` + +--- + +## Verified API facts (source of truth for every claim in the post) + +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.** + +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. + +**Runtime-neutral `Agent` contract** (`libs/chat/src/lib/agent/agent.ts:27`): +- Signals: `messages` (`Message[]`), `status` (`AgentStatus`), `isLoading` (`boolean`), `error` (`AgentError | undefined`), `toolCalls` (`ToolCall[]`), `state` (`TState`) +- Methods: `submit(input, opts?)`, `stop()`, `retry()`, `regenerate(assistantMessageIndex)` +- Optional: `interrupt?`, `subagents?`, `clientTools?` +- Events: `events$` (Observable, required) + +**`AgentWithHistory`** (`libs/chat/src/lib/agent/agent-with-history.ts:13`) adds `history` (`AgentCheckpoint[]`) and optional `messageCheckpoints`. + +**`LangGraphAgent`** (`libs/langgraph/src/lib/agent.types.ts:331`) extends `AgentWithHistory` and adds (selection for the post — do not enumerate all in prose): +- Raw signals prefixed `langGraph*`: `langGraphMessages`, `langGraphInterrupts`, `langGraphToolCalls`, `langGraphHistory` — the prefix exists to avoid collision with the runtime-neutral names +- LangGraph-specific: `value`, `hasValue`, `toolProgress`, `queue`, `branch`/`setBranch`, `isThreadLoading`, `switchThread`, `joinStream`, `activeSubagents`/`getSubagent`/`getSubagentsByType`/`getSubagentsByMessage`, `customEvents`, `lifecycle`, `experimentalBranchTree`, `reload` +- `clientTools` is **required** here (optional on the neutral contract) +- `submit` widens options with `LangGraphSubmitOptions` (resume commands, checkpoint forks) + +**Key facts for the "two types" section:** +- `injectAgent()` (no-arg) returns default-typed `LangGraphAgent`; `injectAgent(ref)` with `createAgentRef()` returns `LangGraphAgent` (per `apps/website/content/docs/langgraph/api/inject-agent.mdx`) +- Everything `` and the other primitives bind lives on the `Agent`/`AgentWithHistory` slice; the LangGraph-specific members are additive +- The AG-UI adapter's `injectAgent()` returns the same neutral slice — that's the swap-runtimes story; link `/docs/choosing-an-adapter` + +--- + +### Task 1: Author the post + +**Files:** +- Create: `apps/website/content/blog/2026-08-26-what-inject-agent-returns.mdx` + +Slug derives from the filename minus the date prefix (`apps/website/src/lib/blog.ts:34`) → `/blog/what-inject-agent-returns`. + +- [ ] **Step 1: Create the file with this exact frontmatter** + +```yaml +--- +title: 'What injectAgent() Actually Returns' +description: 'The signals, the async methods, and the runtime-neutral Agent contract underneath — what you get from one call.' +date: 2026-08-26 +tags: [langgraph, angular, signals, agentic-ui] +author: brian +featured: false +draft: false +--- +``` + +The description is 110 characters — under the 155-char truncation limit in `apps/website/src/lib/docs.ts`. If you edit it, re-count. + +- [ ] **Step 2: Write the lede and body sections** + +Structure (from the approved spec) with per-section content requirements: + +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. +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. +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. +4. **`## Why is the return type two types?`** — the strategic section. `LangGraphAgent` extends the runtime-neutral `Agent` contract. The neutral slice is what `` 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 …"). +5. **`## What does this look like in a component?`** — one snippet, verbatim: + +```ts +import { Component } from '@angular/core'; +import { injectAgent } from '@threadplane/langgraph'; + +@Component({ + selector: 'app-support', + template: ` + @for (message of chat.messages(); track message.id) { +

{{ message.content }}

+ } + @if (chat.isLoading()) { +

Thinking…

+ } + `, +}) +export class SupportComponent { + readonly chat = injectAgent(); + + async send(text: string) { + await this.chat.submit({ message: text }); + } +} +``` + + 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. +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. + +Include the standard Threadplane licensing `` (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. + +- [ ] **Step 3: Voice pass** + +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): + +- Opens by restating the title; no "Introduction" header +- Contractions present; paragraphs 1–3 lines +- H2-as-question scaffolding, each answered in the first line below it +- At least one "Let's" transition per major section +- Opinions flagged ("I think," "For me") +- No hype vocabulary ("blazing," "game-changing"), no marketing CTA +- Every named signal/method exists in the verified facts above — no inventions + +- [ ] **Step 4: Commit** + +```bash +git add apps/website/content/blog/2026-08-26-what-inject-agent-returns.mdx +git commit -m "feat(website): add 'What injectAgent() Actually Returns' blog post" +``` + +--- + +### Task 2: Validate content and site tests + +**Files:** +- No new files; runs existing suites. + +- [ ] **Step 1: Verify frontmatter parses and description length** + +```bash +cd apps/website && node -e " +const matter = require('gray-matter'); +const fs = require('fs'); +const f = matter(fs.readFileSync('content/blog/2026-08-26-what-inject-agent-returns.mdx','utf8')); +console.log('desc length:', f.data.description.length); +if (f.data.description.length > 155) throw new Error('description too long'); +if (!f.data.title || !f.data.date || f.data.author !== 'brian') throw new Error('frontmatter incomplete'); +console.log('OK'); +" +``` + +Expected: `desc length: ` (≤155) then `OK`. If `gray-matter` isn't resolvable this way, check how `apps/website/src/lib/blog.ts` imports it and mirror that. + +- [ ] **Step 2: Run the website test suite** + +`nx test website` does NOT exist (fails silently) — use vitest directly: + +```bash +cd apps/website && npx vitest run --config vite.config.mts +``` + +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. + +- [ ] **Step 3: Render check in the dev server** + +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: + +- The post renders (no MDX compile error page) +- The `` renders as a styled callout, not raw JSX +- The code block renders with highlighting +- The meta description in `` matches the frontmatter (view source or read_page) + +Stop the dev server when done. + +- [ ] **Step 4: Commit any fixes** + +```bash +git add -A apps/website/content/blog/ +git commit -m "fix(website): render fixes for injectAgent post" +``` + +Skip if Step 3 needed no changes. + +--- + +### Task 3: PR + +**Files:** +- None; git/GitHub operations only. + +- [ ] **Step 1: Push the branch and open a PR** + +```bash +git push -u origin HEAD +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). + +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. + +🤖 Generated with [Claude Code](https://claude.com/claude-code)" +``` + +- [ ] **Step 2: Verify the Vercel preview** + +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. diff --git a/docs/superpowers/specs/2026-08-26-blog-sequence-inject-agent-design.md b/docs/superpowers/specs/2026-08-26-blog-sequence-inject-agent-design.md new file mode 100644 index 000000000..5f5eaff3e --- /dev/null +++ b/docs/superpowers/specs/2026-08-26-blog-sequence-inject-agent-design.md @@ -0,0 +1,62 @@ +# Blog sequence + post #1 design: "What `injectAgent()` Actually Returns" + +**Date:** 2026-08-26 +**Status:** Approved (roadmap + post design approved in brainstorming session) +**Source data:** `docs/gtm/blog-topic-candidates.md` (Search Console pull, window 2026-05-25 → 08-23; lives on branch of worktree `suspicious-ellis-644600`) + +## The roadmap + +Brian chose to run all three strategic plays, in this order — one post each, sequentially: + +1. **Convert existing traffic** — two posts: + 1. **#11 "What `injectAgent()` Actually Returns"** (`what-inject-agent-returns`) — `injectagent` is the site's top striking-distance query: 108 impressions at position 5.6, currently landing on an API reference page. + 2. **#9 "json-render vs A2UI: Choosing a Generative UI Contract"** (`json-render-vs-a2ui-choosing`) — highest-intent traffic; already #3 with a 25% CTR phrasing. +2. **Grow search surface** — **#1 "LangGraph Subgraphs: When to Split a Graph and When Not To"** (`langgraph-subgraphs-when-to-split`) — 41 impressions at position 32.3, nothing of ours competes. +3. **Distribution / reputation** — **#12 "Testing Agents Deterministically: Fixture-Replay for LLM UIs"** (`deterministic-agent-ui-testing`) — no search evidence; the flagship shareable piece. + +Each later post gets its own design pass before drafting. This spec fully designs only the first. + +## Post #1: What `injectAgent()` Actually Returns + +**Slug:** `what-inject-agent-returns` +**File:** `apps/website/content/blog/2026-08-26-what-inject-agent-returns.mdx` (date = publish date; adjust if drafting slips) +**Meta description (<155 chars, per `apps/website/src/lib/docs.ts` truncation):** "The signals, the async methods, and the runtime-neutral Agent contract underneath — what you get from one call." + +### Intent and positioning + +Someone typing bare `injectagent` wants "what is this for," not "what is the signature." The existing API page (`apps/website/content/docs/langgraph/api/inject-agent.mdx`) answers signatures well. The post answers the conceptual question and **must not cannibalize the API page** — it links to it for signatures and targets the conceptual intent. + +Chosen angle (of three considered): **the contract tour.** Walk the returned object in three groups — signals, methods, and the runtime-neutral `Agent` contract underneath. The contract section is the strategic payload: it turns "one call" into "and you can swap runtimes later," links `/docs/choosing-an-adapter`, and sets up the next two plays (#9 comparison post; later #19 migration post). + +Rejected angles: a "projection/mental-model" frame (drifts into internals; overlaps a future stream-modes post) and a mini-tutorial (cannibalizes the quickstart and both chat tutorials). + +### Structure + +1. Lede: one sentence restating the title — one call, one object; here's what's actually in it. No "Introduction" header. +2. `## What are the signals?` — the reactive surface you bind templates to. +3. `## What are the methods?` — the imperative surface user actions call. +4. `## Why is the return type two types?` — `LangGraphAgent` vs. the runtime-neutral `Agent` contract, and what that separation buys. Links `/docs/choosing-an-adapter`. +5. `## What does this look like in a component?` — one short, honest snippet. Not a tutorial; link the quickstart for the full path. +6. `## Conclusion` — one paragraph; forward links to the API page, choosing-an-adapter, and the streaming-chat tutorial. + +### Voice and register + +Per `docs/gtm/voice.md` with the 2026 technical-post override (no invented first-person anecdotes, trimmed rhetoric, no emoji, substance over framing): + +- H2-as-question scaffolding, each answered immediately. +- Contractions, short paragraphs (1–3 lines), "Let's" transitions. +- Opinions flagged as opinions where recommendations appear. +- No hype vocabulary, no marketing CTAs; close is a forward link. + +### Accuracy requirements (drafting gate) + +- Enumerate the returned surface **from source** (`libs/langgraph` + the `Agent` contract in `libs/chat`), not from memory. Every signal and method named in the post must exist on the published API. +- Verify claims against the current published release (v0.0.58 line) — main may be ahead of npm. +- Frontmatter must match existing blog conventions (see `2026-08-13-angular-chat-app-tutorial-with-langchain-langgraph.mdx`): title, description, date, tags, `author: brian`, `featured`, `draft`. +- Include the standard Threadplane licensing callout if the post shows `@threadplane/chat` usage. + +### Out of scope + +- Any docs/API-page changes. +- Posts #9, #1, #12 content design (each gets its own pass). +- Search-position tracking changes (GSC harness already live, PR #826).