Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions apps/website/content/docs/agent/api/api-docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -595,49 +595,49 @@
{
"name": "interruptReceivedAt",
"type": "Signal<number | null>",
"description": "Epoch ms of the first interrupt$ non-null in this stream. Resets on clearThread.",
"description": "Epoch ms of the first interrupt$ non-null in this stream. Resets on switchThread().",
"optional": false
},
{
"name": "interruptResolvedAt",
"type": "Signal<number | null>",
"description": "Epoch ms of the most recent submit({ interrupt }) call. Resets on clearThread.",
"description": "Epoch ms of the most recent submit({ resume }) call. Resets on switchThread().",
"optional": false
},
{
"name": "streamErrorAt",
"type": "Signal<object | null>",
"description": "Epoch ms + classification of the most recent stream error. Resets on clearThread.",
"description": "Epoch ms + classification of the most recent stream error. Resets on switchThread().",
"optional": false
},
{
"name": "streamStartedAt",
"type": "Signal<number | null>",
"description": "Epoch ms of the first stream chunk arrival. Resets on clearThread.",
"description": "Epoch ms of the first stream chunk arrival. Resets on switchThread().",
"optional": false
},
{
"name": "threadCreatedAt",
"type": "Signal<number | null>",
"description": "Epoch ms when the agent's \"create new thread\" branch fired. Resets on clearThread.",
"description": "Epoch ms when the agent's \"create new thread\" branch fired. Resets on switchThread().",
"optional": false
},
{
"name": "threadPersistedAt",
"type": "Signal<number | null>",
"description": "Epoch ms when an existing thread was restored from server (proves persistence). Resets on clearThread.",
"description": "Epoch ms when an existing thread was restored from server (proves persistence). Resets on switchThread().",
"optional": false
},
{
"name": "toolCallCompletedAt",
"type": "Signal<number | null>",
"description": "Epoch ms of the first tool call result transition. Resets on clearThread.",
"description": "Epoch ms of the first tool call result transition. Resets on switchThread().",
"optional": false
},
{
"name": "toolCallStartedAt",
"type": "Signal<number | null>",
"description": "Epoch ms of the first tool call append. Resets on clearThread.",
"description": "Epoch ms of the first tool call append. Resets on switchThread().",
"optional": false
}
],
Expand Down
11 changes: 7 additions & 4 deletions apps/website/content/docs/agent/concepts/agent-architecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ export class MultiAgentComponent {
</Tabs>

<Callout type="tip" title="subagentToolNames is the key">
The `subagentToolNames` option tells agent() which tool calls spawn subagents. The default Deep Agents tool name is `task`; set this option when your graph uses custom delegation tool names.
The `subagentToolNames` option tells `agent()` which tool calls spawn subagents. The default Deep Agents tool name is `task`; set this option when your graph uses custom delegation tool names. Ordinary LangGraph subgraph nodes stream through the parent signals, but they do not appear in `subagents()` unless they are represented by matching delegation tool calls.
</Callout>

## Error Handling and Recovery
Expand Down Expand Up @@ -638,14 +638,17 @@ graph = builder.compile()
**Use when:** The agent takes high-stakes actions (sending emails, modifying data, making purchases) that need human approval.

```python
from langgraph.types import Interrupt
from langgraph.types import interrupt

def propose_action(state: AgentState) -> dict:
plan = llm.invoke(state["messages"])
raise Interrupt(value={"action": plan.content, "requires_approval": True})
approval = interrupt({"action": plan.content, "requires_approval": True})
return {"pending_action": plan.content, "approval": approval}

def execute_action(state: AgentState) -> dict:
# Only runs after human approves
if not state.get("approval", {}).get("approved"):
return {"messages": [{"role": "assistant", "content": "Action cancelled."}]}
return perform_action(state["pending_action"])
```

Expand All @@ -663,7 +666,7 @@ builder.add_node("analyst", analyst_subgraph)
builder.add_conditional_edges("supervisor", route_to_agent)
```

**Angular signals used:** `messages()`, `subagents()`, `toolCalls()`, `status()`
**Angular signals used:** `messages()`, `toolCalls()`, `status()`; `subagents()` only when delegation happens through tracked tool calls

### Decision Matrix

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ The UI lifecycle is intentionally boring.
5. `stop()` aborts the active run when supported.
6. `regenerate(index)` rolls back from an assistant message and reruns from the preceding user message.

LangGraph adds deeper lifecycle and history surfaces. `@ngaf/langgraph` exports `AGENT_LIFECYCLE`, `AgentLifecycle`, and `AgentLifecycleRegistry`. Those are useful for telemetry, debugging, persistence, and time-travel UI. They are not required by `@ngaf/chat`.
LangGraph adds deeper lifecycle and history surfaces. `@ngaf/langgraph` exposes `agent().lifecycle` and exports `AgentLifecycle`, `AgentLifecycleRegistry`, and the low-level `AGENT_LIFECYCLE` token. Those are useful for telemetry, debugging, persistence, and time-travel UI. They are not required by `@ngaf/chat`.

## Testing And Mocks

Expand Down
12 changes: 9 additions & 3 deletions apps/website/content/docs/agent/concepts/langgraph-basics.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -176,19 +176,25 @@ const completedTools = computed(() => agent.toolCalls());
The agent proposes an action and pauses. Your Angular UI shows an approval dialog. The user decides, and the agent resumes.

```python
from langgraph.types import Interrupt
from langgraph.types import interrupt

def propose_action(state: State) -> dict:
action = llm.invoke(state["messages"])
# Pause execution — Angular will show approval UI
raise Interrupt(value={
approval = interrupt({
"action": "send_email",
"to": "client@example.com",
"body": action.content,
})
return {
"pending_action": action.content,
"approval": approval,
}

def execute_action(state: State) -> dict:
# Only runs after human approves
if not state.get("approval", {}).get("approved"):
return {"messages": [{"role": "assistant", "content": "Email cancelled."}]}
send_email(state["pending_action"])
return {"messages": [{"role": "assistant", "content": "Email sent."}]}
```
Expand Down Expand Up @@ -227,7 +233,7 @@ builder.add_node("analyst", analyst_subgraph)
builder.add_conditional_edges("supervisor", lambda s: s["next_agent"])
```

**Angular connection:** Track delegated work through dedicated subagent signals:
**Angular connection:** Track delegated work through dedicated subagent signals when delegation is exposed as tool calls:
```typescript
const orchestrator = agent<OrchestratorState>({
assistantId: 'orchestrator',
Expand Down
9 changes: 6 additions & 3 deletions apps/website/content/docs/agent/concepts/state-management.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -209,14 +209,17 @@ The agent doesn't wait until it's finished to send state updates. It streams par

### How Partial Updates Arrive

LangGraph streams in `values` mode by default — each SSE event contains the full state snapshot after a node completes. In `messages` mode, you get individual message tokens as they're generated.
`agent()` requests the stream modes it needs to populate its public signals by default. Use `values` when you only need state snapshots, and `messages-tuple` when you only need individual message tokens.

```typescript
const agent = agent<ProjectState>({
assistantId: 'project_agent',
// Default: values mode — full state after each node
// streamMode: 'messages' — token-by-token for text fields
});

agent.submit(
{ message: 'Update the project plan.' },
{ streamMode: ['messages-tuple'] },
);
```

### Signals Update Mid-Stream
Expand Down
11 changes: 8 additions & 3 deletions apps/website/content/docs/agent/getting-started/installation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ npm install @ngaf/langgraph @ngaf/chat

## Configure the provider

Add `provideAgent()` to your application configuration. This sets global defaults for all agent instances.
Add `provideAgent()` to your application configuration. This sets global defaults for all agent instances. It is recommended when most agents share the same LangGraph URL, but it is not required if each `agent()` call passes its own `apiUrl`.

```typescript
// app.config.ts
Expand Down Expand Up @@ -102,10 +102,15 @@ Create a minimal component to verify the setup works. `agent()` must be called i

```typescript
// In a component field initializer (injection context)
const test = agent({ assistantId: 'chat_agent' });
const test = agent({
apiUrl: 'http://localhost:2024',
assistantId: 'chat_agent',
});
console.log(test.status()); // 'idle' - setup is correct
```

If you already configured `provideAgent({ apiUrl })`, you can omit `apiUrl` from individual agents.

## Troubleshooting

<Callout type="warning" title="Common issues">
Expand All @@ -116,7 +121,7 @@ console.log(test.status()); // 'idle' - setup is correct

**Connection refused** -- If you see `ERR_CONNECTION_REFUSED`, verify your LangGraph server is running and that the `apiUrl` matches the correct host and port. Run `langgraph dev` and confirm the server starts at the expected address (default `http://localhost:2024`).

**"NullInjectorError: No provider for AgentConfig"** -- You forgot to add `provideAgent()` to your `appConfig` providers array. See the [Configure the provider](#configure-the-provider) section above.
**Missing or incorrect API URL** -- `provideAgent()` is optional, but the runtime still needs an API URL from either `provideAgent({ apiUrl })` or `agent({ apiUrl })`. If the URL is missing or points at the wrong origin, the SDK request will fail when you submit the first run.

</Callout>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ Your Angular app is a stateless client. All agent state — threads, checkpoints
Deterministic testing with MockAgentTransport
</Card>
<Card title="Lifecycle Signals" href="/docs/agent/guides/lifecycle">
Subscribe to per-agent lifecycle signals via AGENT_LIFECYCLE
Read per-agent lifecycle signals for debugging and telemetry
</Card>
<Card title="Angular Signals" href="/docs/agent/concepts/angular-signals">
Deep dive into how Signals power agent
Expand Down
62 changes: 27 additions & 35 deletions apps/website/content/docs/agent/guides/deployment.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ Angular uses file-based environment replacement at build time rather than `proce
export const environment = {
production: false,
langgraphUrl: 'http://localhost:2024',
langsmithApiKey: '', // not needed locally
};
```

Expand All @@ -97,8 +96,7 @@ export const environment = {
```typescript
export const environment = {
production: true,
langgraphUrl: 'https://my-agent-abc123.langgraph.app',
langsmithApiKey: 'lsv2_pt_xxxxxxxx',
langgraphUrl: '/api/langgraph',
};
```

Expand All @@ -124,50 +122,37 @@ Angular CLI replaces `environment.ts` with `environment.prod.ts` during `ng buil

## Authentication

### API key for LangGraph Platform
### Keep LangGraph credentials server-side

LangGraph Cloud deployments require an API key on every request. The recommended approach is an Angular HTTP interceptor that attaches the key as a header.
Do not ship LangSmith or LangGraph API keys in an Angular bundle. The default `FetchStreamTransport` uses the LangGraph SDK client directly, not Angular `HttpClient`, so Angular HTTP interceptors do not attach headers to `agent()` requests.

```typescript
import { HttpInterceptorFn } from '@angular/common/http';
import { environment } from '../environments/environment';
For production, put a same-origin backend route, edge function, or API gateway in front of LangGraph. The browser calls your relative URL, and that server-side layer adds the deployment credentials.

export const langGraphAuthInterceptor: HttpInterceptorFn = (req, next) => {
if (req.url.startsWith(environment.langgraphUrl)) {
const cloned = req.clone({
setHeaders: {
'x-api-key': environment.langsmithApiKey,
},
});
return next(cloned);
}
return next(req);
```typescript
// environment.prod.ts
export const environment = {
production: true,
langgraphUrl: '/api/langgraph',
};
```

Register the interceptor in your application config:

```typescript
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { langGraphAuthInterceptor } from './auth.interceptor';

export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(withInterceptors([langGraphAuthInterceptor])),
provideAgent({
apiUrl: environment.langgraphUrl,
}),
],
};
```

<Callout type="warning" title="Never commit API keys">
Add `environment.prod.ts` to `.gitignore`. In CI, generate it from environment variables or inject secrets at build time.
<Callout type="warning" title="Never expose API keys">
Environment files that are bundled into Angular are public. Store LangGraph deployment credentials in your server-side proxy, hosting provider secret store, or CI/CD environment, not in `environment.prod.ts`.
</Callout>

### User-level authentication

If your app has its own user authentication (JWT, session cookies), you can add a second interceptor or extend the one above to forward identity headers that your agent can use for per-user scoping.
If your app has its own user authentication, authenticate the same-origin proxy with cookies or your usual app session. The proxy can forward user identity to LangGraph through safe, server-controlled metadata or headers.

## CORS configuration

Expand All @@ -185,7 +170,7 @@ In `langgraph.json`, add an `http` section:
"cors": {
"allow_origins": ["https://your-angular-app.com"],
"allow_methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
"allow_headers": ["Content-Type", "x-api-key", "Authorization"],
"allow_headers": ["Content-Type", "Authorization"],
"allow_credentials": true
}
}
Expand All @@ -202,6 +187,7 @@ Production apps need graceful error handling. Build a reactive error boundary us

```typescript
import { ChangeDetectionStrategy, Component, computed } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { agent } from '@ngaf/langgraph';

@Component({
Expand Down Expand Up @@ -254,7 +240,7 @@ export async function retrySubmit(
): Promise<void> {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
chat.submit(input);
await chat.submit(input);
return;
} catch {
if (attempt === maxAttempts - 1) throw new Error('Max retries exceeded');
Expand All @@ -269,9 +255,16 @@ export async function retrySubmit(
Use `joinStream()` to reconnect to a running agent execution after a network interruption, page refresh, or navigation event.

```typescript
// Store the run ID when starting a stream
const runId = this.chat.runId();
localStorage.setItem('activeRunId', runId);
// Store the run ID when LangGraph creates the run
await this.chat.submit(
{ message: input },
{
streamResumable: true,
onRunCreated: ({ run_id }) => {
localStorage.setItem('activeRunId', run_id);
},
},
);

// After reconnecting, resume from where the stream left off
const savedRunId = localStorage.getItem('activeRunId');
Expand Down Expand Up @@ -323,7 +316,6 @@ jobs:
export const environment = {
production: true,
langgraphUrl: '${{ secrets.LANGGRAPH_URL }}',
langsmithApiKey: '${{ secrets.LANGSMITH_API_KEY }}',
};
EOF
- run: npx ng build --configuration production
Expand Down Expand Up @@ -371,10 +363,10 @@ effect(() => {

<Steps>
<Step title="Set production apiUrl">
Point `provideAgent({ apiUrl })` to your LangGraph Cloud deployment URL via `environment.prod.ts`.
Point `provideAgent({ apiUrl })` to your same-origin LangGraph proxy, or to a direct LangGraph URL only when that deployment is intentionally public.
</Step>
<Step title="Configure authentication">
Add an HTTP interceptor to attach `x-api-key` headers to all LangGraph requests.
Route browser traffic through a server-side proxy or gateway that adds LangGraph credentials outside the Angular bundle.
</Step>
<Step title="Set up CORS">
Add your Angular app's origin to the `allow_origins` list in `langgraph.json`.
Expand Down
Loading
Loading