LANGGRAPH · August 26, 2026 · 3 min read
What injectAgent() Actually Returns
The signals, the async methods, and the runtime-neutral Agent contract underneath — what you get from one call.
You call injectAgent() once, you get one object back — this post is about what's actually in it.
The API page 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 aMessage[], updated as tokens stream in. This is what you@forover.status— where the agent is in its run lifecycle, as a single value you can switch on.isLoading—truewhile a run is in flight. The signal behind every "Thinking…" indicator.error— the last run's failure, orundefined. Render it; don'ttry/catchyour 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 yourTwhen 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<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).
Everything above — the six signals, the four methods — lives on that neutral contract.
The neutral slice is what <chat> 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 walks through the tradeoff.
What does this look like in a component?
Let's put the whole thing in one small component:
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 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 has the full signatures, 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.