LangGraph Agents
Production-grade LangGraph.js (Node 24+, TypeScript strict, @langchain/langgraph).
Owns runtime doctrine — placement, prompt/capability injection, graph-spec sync. Diffs via ns-coder or ns-autonomous. Framework choice: ns-agent-architecture.
Applicability
| Context | Doctrine strength |
|---|---|
| Greenfield agent-api (new LangGraph runtime) | MUST follow build workflow gates — dev-chat, budgets, normalize-before-truncate, separate skill cap. Topology: suggest plan_execute for most MCP/tool-heavy cases — lock in graph-spec.md; not a hard rule |
| Brownfield existing agent | RECOMMENDED migration toward same controls; orphan recovery does not Critical-fail missing topology |
| Intentional MCP redesign | Sync graph-spec.md + refs in same delivery — treat greenfield MUST for topology/budget/evidence sections touched |
Brownfield open ReAct valid until deliberate topology change. Greenfield MUST = new agent-api + intentional MCP redesign only.
Routing (read first)
| Signal | Action |
|---|---|
| Conceptual design unlocked (agent vs RAG vs fine-tune) | Stop → ns-agent-adaptation |
| No framework lock / CrewAI requested | Stop → ns-agent-architecture |
| Orphan / lost structure / layout unclear | Run orphan checklist before features (references/orphan-recovery-checklist.md) |
GitLab ISSUE_URL or SDD version scope |
Defer to harness ../../ns-harness/references/code-skill-routing.md — do not absorb |
| Approved placement/inject plan ready for diff | Hand off to ns-coder for implementation — except greenfield bootstrap copy (references/bootstrap-agent-runtime.md) |
Boot (mandatory)
See ../../ns-harness/references/session-boot.md — complete Session boot (blocking) there, then:
- Confirm
{agent_api_root}andgraph-spec.mdwhen touching runtime - Load placement/inject refs before path decisions (
references/placement-and-domains.md,references/prompt-and-capability-injection.md) - Continue this skill
Success: placement + inject doctrine + project rules. Failure: invented folders or external frameworks.
When to use
| Situation | Action |
|---|---|
Greenfield agent-api (no {agent_api_root}/package.json) |
Build workflow Phase 0 then Phase 1 bootstrap — references/bootstrap-agent-runtime.md |
| Brownfield / orphaned runtime | Run Orphan recovery (references/orphan-recovery-checklist.md) before features |
| New file / unclear folder | Placement Decision Block + references/placement-and-domains.md |
| System prompt / skill inject / bind | Prompt/Capability plan + references/prompt-and-capability-injection.md |
Topology / state / capabilities / guard_fail_mode change |
Spec Sync Gate — update graph-spec.md in the same delivery |
| MCP with many servers/tools | Read references/mcp-complex-access.md + references/capability-governance.md |
| Token blow-up / slow turns | Read references/context-window-and-tokens.md |
| Untrusted input / injection / claimed authority | Read references/guardrail-and-adversarial.md — scope classifier; scaffold fail-open ≠ doctrine |
| Cost-sensitive high volume / model tier / cache | Read references/model-cascade-and-cache.md |
| Provider message/reasoning quirks | Read references/message-content-blocks.md |
| HITL / streaming UX | Read references/streaming-and-hitl.md |
| JSON planner / analyst chooses tools | Operator-progress channel — templates/contracts/planner-contract.md + references/streaming-and-hitl.md |
| Evals before merge | Read references/evals-and-gates.md |
Core doctrine
LangGraph = control flow. MCP/local tools = capabilities under graph. Small graph state (refs, summaries). Checkpointer = full state; context window = LLM view — separate.
System prompt: compose base_invariant (motor) + injected (product persona) per LLM invoke. Never persist composed system/persona text in graph state, checkpointer, or durable messages. Summary SystemMessage at index 0 ≠ full system — references/prompt-and-capability-injection.md, references/message-content-blocks.md.
Locale: conversation-observed turnLocale (detection-first from human messages ± intent slots); configurable.locale weak hint only; Intl formatters in code — not fixed bootstrap locale. references/evidence-and-fidelity.md, templates/snippets/conversation-locale.ts.snippet.
Operator progress (JSON planner hops): greenfield streaming_sse with a planner/analyst that emits structured executionPlan (no bindTools on that hop) MUST persist userFacingIntent (or analysis.userFacingIntent) + executionPlan on AgentState. userFacingIntent language MUST match the current user message (last HumanMessage) — not English unless that message is English; not product default locale. Machine intent stays English for audit. Emit SSE thinking from that field at node entry of the next hop — not response_streaming, not in durable messages. Hop 0 uses generic copy from conversation/presentation/ (or locale) in the same operator language. Open ReAct + ToolNode uses tool_started / tool_finished only. Details: templates/contracts/planner-contract.md, references/streaming-and-hitl.md.
Three capability kinds bind to the model:
| Kind | LLM wire name | Internal id |
|---|---|---|
| Local tool | {name} |
local:{name} |
| MCP tool | mcp__{server}__{tool} |
mcp:{server}:{tool} |
| Skill procedure | use_skill__{id} |
skill:{id} |
Wire names must match ^[a-zA-Z0-9_-]{1,128}$ (use __ separators; colons only in internal ids). Colon in a new wire name is Critical on review.
Pre-change gates
Before new file or inject/bind change: complete all three. No code until posted.
1. Placement Decision Block
### Placement Decision Block
- Artifact: …
- Type: …
- Target path: …
- Layer: …
- Refs: placement-and-domains.md
- do_not_create_under: […]
Full matrix: references/placement-and-domains.md.
2. Prompt / Capability plan
### Prompt / Capability plan
- Compose: base_invariant + injected (rebuild per invoke; not in state/checkpointer/durable messages)
- Motor (`base_invariant`): [gather-no-Markdown / sole-writer / tool discipline / JSON planner userFacingIntent is SSE not Markdown / …]
- Product (`injected`): canonical path + persona/tone notes; mode-resolved: yes/no; modes: [...]; resolver: ...
- System layers touched: […]
- Canonical prompt path: …
- Session overlay: yes/no
- Bind list: […]
- Auto-inject skills: […] (exclusive of bind for same id)
- Truncate caps: tool vs skill body
- Bind parity: …
- Spec paths to sync: […]
Full doctrine: references/prompt-and-capability-injection.md. Plan/mode-resolve: references/prompt-and-capability-injection-plan-execute.md.
3. Spec Sync Gate
Nodes, edges, state, capabilities, recursion_limit, wire names, or guard_fail_mode change: update graph-spec.md same delivery. Stale archive ≠ SoT — sync spec to intended runtime; do not force-fit live code to stale archive.
Reference map
Load on demand — do not memorize whole files.
| Reference | Read when |
|---|---|
references/bootstrap-agent-runtime.md |
Greenfield copy of templates/agent-runtime/ |
references/orphan-recovery-checklist.md |
Project structure unclear or agent "lost" |
references/runtime-layout.md |
Scaffolding, refactors, layer violations |
references/placement-and-domains.md |
Where to put files; domain vs graph vs config |
references/prompt-and-capability-injection.md |
System prompt layers, bind vs inject, bind parity |
references/prompt-and-capability-injection-plan-execute.md |
Prompt/Capability plan; plan_execute gather vs composer; mode-resolved injected |
references/message-content-blocks.md |
AIMessage/HumanMessage/ToolMessage across providers |
references/context-window-and-tokens.md |
trim, summarize, tool vs skill body caps, context_manager |
references/mcp-complex-access.md |
Multi-server MCP, discovery, transport, lifecycle |
references/capability-governance.md |
Allowlist, classification, rate limits, tool budgets |
references/evidence-and-fidelity.md |
State-backed evidence, fidelity gate, conversation-observed locale |
templates/snippets/conversation-locale.ts.snippet |
resolveConversationLocale + Intl formatUserFacing |
templates/snippets/tool-budget.ts.snippet |
Per-turn tool/MCP caps, arg fingerprint duplicate-skip |
templates/snippets/prepare-llm-messages.ts.snippet |
context_manager helper |
references/error-and-reliability.md |
Tool errors, circuit breaker, retries, compensation, parallel staleness, cost stop |
references/model-cascade-and-cache.md |
Cheap-first cascade, both signals OR-escalate, semantic/prompt cache; not LLM intent_classify hop |
references/observability.md |
Postgres audit, retention = tenant clock, query tenant+period+one decision, cost reservation |
references/architectures.md |
ReAct, plan_execute (suggested start for most MCP), Reflection levels, other topologies; node id ≠ state channel |
references/guardrail-and-adversarial.md |
Scope/safety classifier; fail-open scaffold ≠ graph-spec lock; not Gateway routing |
references/streaming-and-hitl.md |
SSE envelopes, operator thinking from planner state, interrupt(), Command resume |
templates/contracts/planner-contract.md |
JSON planner hops: executionPlan + userFacingIntent |
references/evals-and-gates.md |
Architecture, tool-selection, memory evals; golden-set promotion |
references/anti-patterns.md |
Review gate before marking done — hot path |
references/anti-patterns-extended.md |
Dead prompt copies, state/memory, reliability, graph, MCP, LLM, ops, process |
Templates: templates/agent-runtime/ (greenfield tree), templates/graph-spec.md, templates/contracts/, templates/snippets/ (brownfield patches).
Session inputs
| Variable | Required |
|---|---|
{agent_api_root} |
Default agent-api |
{task} |
What to build, fix, or review |
Orphan recovery (brownfield first)
Runtime disorganized or team blocked:
- Read
references/orphan-recovery-checklist.md; score project. - Gap report: structure, placement, context window, inject/bind parity, MCP governance, HTTP, spec sync.
- Ordered fix plan (one phase per message if large).
- Implement via
ns-coder.
No new graph nodes or MCP servers until layout + governance baselines pass.
Build workflow (greenfield or post-recovery)
Phase 0 — Spec gate
If graph-spec.md is missing, create it from templates/graph-spec.md. Minimum sections: locked header (framework, architecture, interaction_mode, guard_fail_mode), domain ownership, prompt composition, state schema, nodes table, edges, interrupts, memory, capability bind/inject table, recursion_limit, HTTP routes.
If the user has no architecture decision yet, stop and invoke ns-agent-architecture first.
Phase 1 — Skeleton
If {agent_api_root}/package.json is missing, run scripts/bootstrap-agent-runtime.mjs (references/bootstrap-agent-runtime.md). Then npm install && npm test in {agent_api_root}.
Do not assemble the tree from templates/snippets/. Do not copy another product. After copy: align src/graph/ with graph-spec.md; put domain in conversation/ + config/ via later phases / ns-coder.
Brownfield existing tree: align per references/runtime-layout.md — snippets for patches only.
Phase 2 — State and persistence
After bootstrap, verify these exist; do not re-scaffold. Fill only gaps vs graph-spec.md.
AgentStatewithmessagesreducer (Annotation.Rootor Zod +MessagesZodMeta).PostgresSaverin dev/prod;MemorySaveronly intests/setup.ts.- Every invoke/stream:
configurable.thread_idviabuildRunConfig. - JSON planner/analyst (no
bindToolson that hop): declareexecutionPlan+userFacingIntent(or nested onanalysis) ingraph-spec.mdstate schema —templates/snippets/state.ts.snippet.
Phase 3 — LLM and messages
- Provider config in
src/llm/(infra only — no domain prompts). - Prefer JSON mode + Zod parse for structured turns; avoid
withStructuredOutputon OpenAI-compatible local servers. - Normalize provider output via
contentBlocks/content_blocks— seereferences/message-content-blocks.md.
Phase 4 — Context window (mandatory)
Implement per references/context-window-and-tokens.md:
trimMessagesForLlmbefore every LLM call.normalizeMcpToolResultthentruncateToolOutputbeforeToolMessageenters state.- Separate
CONTEXT_SKILL_BODY_MAX_CHARSfor skill bodies (snippetskillBodyMaxChars). - Optional
summarizeOlderMessageswith persisted compaction (RemoveMessage+ rewrite) in the same agent-node return.
Never pass raw state.messages to the model.
Phase 5 — Capabilities
- Local
StructuredTools insrc/tools/. - MCP: governed client — discovery → local allowlist → wire names → singleton client lifecycle (
references/mcp-complex-access.md). - Skills:
skills/*.mdauto-discovered →use_skill__{id}or auto-inject (exclusive per id).
Apply references/capability-governance.md and references/prompt-and-capability-injection.md before bindTools. Enforce bind parity. Wire per-turn tool/MCP budgets from templates/snippets/tool-budget.ts.snippet when MCP or external tools are bound.
Phase 6 — HTTP and interaction mode
| Mode | Requirements |
|---|---|
sync_json |
POST /threads, POST /threads/:id/message |
streaming_sse |
SSE envelope per references/streaming-and-hitl.md; greenfield MUST ship GET /dev-chat gated by DEV_CHAT_ENABLED (local-only); JSON planner hops MUST emit operator thinking from state userFacingIntent |
| HITL | interrupt() + POST /threads/:id/resume with Command({ resume }); always-escalate MUST send approver_id + approver_role |
Brownfield missing dev-chat: recommend add — not Critical. Postman synced with live routes.
Phase 7 — Observability
Wire references/observability.md: initDb, runStorage, logLlmCall, logToolExecution, persist turn_decisions. LangSmith and OTel are opt-in.
Phase 8 — Evals and review
- Add suites per
references/evals-and-gates.md. - Run
npm run build && npm testin{agent_api_root}. - Invoke
ns-revieweron the diff; ask it to verify placement, inject, wire-name, and bind-parity anti-patterns when the diff touchesagent-api.
Maintenance workflow
Ongoing work (not greenfield):
- Three Pre-change gates
graph-spec.mdmatches intended graph after change- Layer: graph node, conversation, MCP, memory, HTTP
- Read matching reference before edit
- Minimal diff via
ns-coderwith placement/inject handoff - Postman on HTTP route change
- Re-run orphan checklist items touched
MCP complex access (quick rules)
Multiple MCP servers, overlapping or large catalogs:
- Discovery filter — allowlisted
tools/listbefore bind - Local classify —
read | write | destructive | admin; never trust server read-only flags - Singleton client — one
MultiServerMCPClientper process; no per-request stdio in prod - Transport — Streamable HTTP deployed; stdio local single-user dev only
- Errors — MCP
isError: true= recoverableToolMessagestatus: "error"; protocol fail may abort with HTTP/SSEfailed - Secrets — env or
configurable/ request payload only; never state or checkpointer
Full: references/mcp-complex-access.md.
Handoff to ns-coder
When implementation is approved, delegate with:
## LangGraph implementation task
- Root: {agent_api_root}
- Spec: path/to/graph-spec.md
- Phase: [number and name from this skill]
- target_paths: […]
- layer: […]
- do_not_create_under: […]
- injection_notes: [layers / bind vs auto-inject / caps]
- spec_paths_to_sync: […]
- References to apply: [list]
- Acceptance: build + test pass; orphan checklist items [n] resolved; placement + inject + bind parity verified
- Review: ns-reviewer after tests — must check placement, inject, wire names (`:`), bind parity (load ns-langgraph-agents anti-patterns when diff touches agent-api)
Stay here for diagnosis, spec, placement, governance design, and greenfield bootstrap copy. ns-coder for feature diffs. Version-scoped features: ns-spec-driven.
Stop conditions
| Condition | Action |
|---|---|
No graph-spec.md and user wants code now |
Create spec or invoke architect |
| Greenfield assembled from snippets / another product tree | Stop; run scripts/bootstrap-agent-runtime.mjs |
| Path outside placement matrix / inventing folders | Stop; propose legal path |
Domain / locale / copy landing in graph/ or llm/ |
Stop; reroute to conversation/config |
| Bind without parity (dispatchable but unbound) | Stop; fix bind or document unbound + test |
: in a new wire name |
Stop; use __ separators |
| Skill auto-inject + bind same id without explicit decision | Stop; choose one mode |
| CrewAI requested | Redirect to appropriate skill |
| Change spans >3 layers without plan | One-line phased plan, wait for approval |
| Critical security gap (secrets in state, ungoverned MCP) | Block feature work; fix governance first |
Related skills (ownership)
| Skill | Owns |
|---|---|
ns-langgraph-agents |
Doctrine, placement, inject plan, graph-spec, greenfield scaffold |
ns-coder |
Feature diffs + review loop (not initial scaffold copy) |
ns-reviewer |
Verdict; when diff touches agent-api, apply placement + inject + wire-name + bind-parity anti-patterns from this skill |
ns-agent-adaptation |
Conceptual adaptation design before architecture |
ns-agent-architecture |
Framework choice before Phase 0 when unlocked |
ns-spec-driven |
Version features after scaffold exists (or first task = bootstrap) |
ns-investigator |
Runtime debug |
Forbidden
- Emitting planner
userFacingIntentasresponse_streamingor as Markdown inmessages(SSEthinkingonly; composer remains sole Markdown writer) - Writing
userFacingIntentin a language other than the current user message (e.g. English progress when the operator wrote Portuguese) - Persisting composed system/persona prompt (
base_invariant + injected) — or secrets/API keys — in graph state, checkpointer, or durablemessages(rebuild system text per invoke) - Treating bootstrap /
.env/configurable.localeas primary locale SoT, or persisting sticky thread locale (use conversation-observedturnLocale+ Intl) - Passing unbounded tool/MCP output into
state.messages - Applying tool/MCP truncate caps to skill bodies (use
CONTEXT_SKILL_BODY_MAX_CHARS) - Trusting MCP tool metadata for security classification
- Spawning stdio MCP subprocesses per HTTP request in production
memory/compilingStateGraph- Domain qualify/conversation prompts in
src/llm/ - Locale/presentation under
graph/ - Nudge as fake
HumanMessage - Tests under
src/ - Greenfield tree from snippets or another product — use
templates/agent-runtime/only