Skip to content

Commit a09f77a

Browse files
bloveclaude
andauthored
docs(deep-agents): five capability pages teach through their running examples (#1035)
* docs(deep-agents): the five deep-agents pages leave the pending list Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(deep-agents): filesystem teaches through the running example Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(deep-agents): memory teaches through the running example Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(deep-agents): planning teaches through the running example Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(deep-agents): subagents teaches through the running example Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(deep-agents): skills teaches through the running example Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(deep-agents): filesystem page — file records, the badge flag, decision counts, cards Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(deep-agents): planning page — the example's docstring stops claiming a default todo middleware; wording precision Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(deep-agents): memory page — customEvents is per run; the example's comments name history hydration Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(deep-agents): subagents page — the middleware ships by default; attribution is by description; example comments corrected Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(deep-agents): skills page — both private keys, read-only by convention, one sentence after each block Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(deep-agents): customEvents is per run on the skills page; info callout; the orchestrator docstring matches the page Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 2c734fb commit a09f77a

21 files changed

Lines changed: 520 additions & 1019 deletions

File tree

Lines changed: 96 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,111 +1,122 @@
11
---
22
title: Filesystem
3-
description: StateBackend keeps a Deep Agents workspace on the graph state, and a FilesystemPermission in interrupt mode routes writes through the chat interrupt panel.
3+
description: How the Deep Agents filesystem example keeps the agent workspace on graph state and pauses writes under /reports/ for human approval
44
---
55

66
# Filesystem
77

8-
`create_deep_agent` always installs `FilesystemMiddleware`, so a Deep Agents agent always has `ls`, `read_file`, `write_file`, and `edit_file`. What decides whether a user interface can render that workspace is not the middleware. It is the backend.
9-
10-
```python
11-
from deepagents import create_deep_agent
12-
from deepagents.backends import StateBackend
13-
from deepagents.middleware import FilesystemPermission
14-
15-
graph = create_deep_agent(
16-
model=ChatOpenAI(model="gpt-4.1", temperature=0),
17-
tools=[lookup_field_elevation, lookup_runway_length],
18-
system_prompt=(PROMPTS_DIR / "filesystem.md").read_text(),
19-
backend=StateBackend(),
20-
permissions=[
21-
FilesystemPermission(operations=["write"], paths=["/reports/**"], mode="interrupt"),
22-
],
23-
)
24-
```
8+
`create_deep_agent` always installs `FilesystemMiddleware`, so a Deep Agents agent always has `ls`, `read_file`, `write_file`, and `edit_file`. What decides whether a user interface can render that workspace is not the middleware. It is the backend. The running example is a dispatch filing desk that gathers airport data, keeps working notes, and files a report, and this page walks the three files behind its workspace panel and its write approval.
259

26-
`StateBackend` stores the agent's files on the graph state under `files`, which means every write arrives at the client as a `values` update. A backend that writes anywhere else — a host directory, a remote object store — puts nothing on the state, and a panel bound to `files` stays empty no matter how busy the agent is. The choice of backend is the choice of whether the workspace is renderable at all.
10+
## What the demo does
2711

28-
## What the demo shows
12+
The Run tab shows the prebuilt `<chat>` composition beside a workspace panel. The welcome suggestion, "Runway note for KASE", asks the agent to work up a runway suitability note: save the raw lookups to `/notes/kase-data.md`, then write the finished note to `/reports/kase-runway.md`.
2913

30-
The demo is the same dispatch desk, given a task that produces artifacts: gather field data for two airports, keep working notes, then file a report.
14+
The scratch file under `/notes/` appears in the panel the moment the agent writes it. The report does not. A write under `/reports/` pauses the run, and an approval card appears below the tree while the target path is already listed as a dimmed, italic row badged "awaiting approval".
3115

32-
The workspace panel renders a directory tree grouped by path. Scratch files under `/notes/` appear the moment the agent writes them, with no ceremony. A write under `/reports/`, however, stops the run.
16+
Accept lets the write land and the run continue. Ignore rejects it, and the agent finishes without the file. The card also offers Edit and Respond, which this example leaves unhandled. Selecting any file in the tree shows its contents in the preview underneath.
3317

34-
That is the `FilesystemPermission` above. In `interrupt` mode a matching call pauses for human approval instead of executing, and the pause surfaces through the standard chat interrupt panel — the same component every other LangGraph interrupt uses. There is no Deep Agents specific interrupt UI, because there is no Deep Agents specific interrupt.
18+
## How it is built
3519

36-
Approving the write lets the run continue and the file lands in the tree. Rejecting it returns the model to work without the file.
20+
Three files carry the feature: a Python graph that builds the agent, an application config that registers it, and an Angular component that projects the workspace and maps the approval buttons onto resume payloads. Open the Code tab to read them in place.
3721

38-
<Callout type="warning" title="Anchor the permission pattern">
39-
Give the pattern a literal prefix, as `/reports/**` does. Bulk tools such as `ls`, `glob`, and `grep` decide whether to fire the permission based on whether their search subtree could overlap the anchored prefix. A fully unanchored pattern collapses to the root and fires on every listing, which turns an approval gate into an interruption on each directory read.
40-
</Callout>
22+
### The lookups the notes are made of
4123

42-
## How it reaches the UI
43-
44-
### The tree
45-
46-
`files` is a flat map from absolute path to contents. Splitting each key on its last slash is enough to group it into directories.
47-
48-
```ts
49-
protected readonly files = computed<WorkspaceFile[]>(() => {
50-
const raw = (this.agent.value() as Record<string, unknown> | undefined)?.['files'];
51-
const entries = new Map<string, string>();
52-
if (raw && typeof raw === 'object') {
53-
for (const [path, contents] of Object.entries(raw as Record<string, unknown>)) {
54-
entries.set(path, typeof contents === 'string' ? contents : JSON.stringify(contents));
55-
}
56-
}
57-
return [...entries.entries()].map(([path, contents]) => {
58-
const slash = path.lastIndexOf('/');
59-
return {
60-
path,
61-
directory: slash > 0 ? path.slice(0, slash) : '/',
62-
name: path.slice(slash + 1),
63-
contents,
64-
};
65-
});
66-
});
67-
```
24+
The agent needs something to write down. Two ordinary LangChain tools answer field elevation and runway length for a handful of ICAO codes, and the system prompt tells the agent to gather that data before it writes anything.
25+
26+
<ExampleCode file="graph.py" region="lookup-tools" title="graph.py — the lookup tools" />
27+
28+
Nothing about these tools is filesystem specific; they are the source of the content the agent files.
29+
30+
### The backend that makes the workspace renderable
31+
32+
`StateBackend` stores the agent's files on the graph state under `files`, so every write reaches the client as a `values` update. A backend that stores files anywhere else, such as a host directory or a remote store, puts nothing on the state, and a panel bound to `files` stays empty no matter how busy the agent is. `FilesystemPermission` is the second half: a rule over operations and path patterns, and in `interrupt` mode a matching call pauses for human approval instead of executing.
6833

69-
### The pending write
34+
<ExampleCode file="graph.py" region="agent" title="graph.py — the agent" />
7035

71-
While an approval is open, the file does not exist yet — it is an argument on a paused tool call. Reading it off the interrupt lets the tree show the file as a ghost row, so the reviewer sees where it is about to land before deciding.
36+
`StateBackend` comes from `deepagents.backends` and `FilesystemPermission` from `deepagents.middleware`; no interrupt wiring is needed beyond the rule, because an interrupt-mode rule auto-installs `HumanInTheLoopMiddleware`.
7237

73-
The interrupt payload is `{ action_requests: [{ name, args }] }`, and for `write_file` the target path is `args.file_path`:
38+
### The agent provider
7439

75-
```ts
76-
protected readonly pendingPath = computed<string | null>(() => {
77-
for (const interrupt of this.agent.langGraphInterrupts() ?? []) {
78-
const value = (interrupt as { value?: unknown }).value as
79-
| { action_requests?: Array<{ args?: Record<string, unknown> }> }
80-
| undefined;
81-
for (const request of value?.action_requests ?? []) {
82-
const path = request.args?.['file_path'];
83-
if (typeof path === 'string') return path;
84-
}
85-
}
86-
return null;
40+
`provideAgent()` registers the agent once for the whole application, and `provideChat({})` registers the configuration the `<chat>` composition reads, here left at its defaults. The example passes a factory because it resolves its connection details at runtime from the host that serves the demo.
41+
42+
<ExampleCode file="app.config.ts" />
43+
44+
Your own application does not need the factory. Pass the values directly:
45+
46+
```typescript
47+
provideAgent({
48+
apiUrl: 'https://your-deployment.langgraph.app',
49+
assistantId: 'da-filesystem',
8750
});
8851
```
8952

90-
### The resume payload
53+
`assistantId` must match the graph name in `langgraph.json`, here `da-filesystem`.
9154

92-
`deepagents` expects a structured decision, not a bare string and not a bare list:
55+
### The pending write, read off the interrupt
9356

94-
```ts
95-
protected onInterruptAction(action: InterruptAction): void {
96-
if (action === 'accept') {
97-
void this.agent.submit({ resume: { decisions: [{ type: 'approve' }] } });
98-
} else if (action === 'ignore') {
99-
void this.agent.submit({ resume: { decisions: [{ type: 'reject' }] } });
100-
}
101-
}
102-
```
57+
While an approval is open the file does not exist yet. It is an argument on a paused tool call, so the only place to find it is the interrupt payload, which `injectAgent()` exposes as the `langGraphInterrupts()` Signal. The payload is `{ action_requests: [{ name, args }], review_configs: [...] }`, and for `write_file` the target path is `args.file_path`.
58+
59+
<ExampleCode file="filesystem.component.ts" region="pending-path" title="filesystem.component.ts — the pending path" />
60+
61+
Reading it lets the tree show the file before it lands, so the reviewer sees where the write is headed while deciding.
62+
63+
### The file map, projected into a tree
64+
65+
`files` is a flat map from absolute path to a file record; the text is on its `content` field, which is why the projection stringifies anything that is not already a string. `agent.value()` returns the live graph state that holds the map. The projection reads that map, adds the pending path as a ghost entry when one is open, and splits each key on its last slash to derive a directory and a name.
66+
67+
<ExampleCode file="filesystem.component.ts" region="files" title="filesystem.component.ts — the file projection" />
10368

104-
Passing a bare list raises a `TypeError` on the server rather than a validation error the browser can show, so the failure appears as a dead run rather than as a rejected submission. The shape is worth getting right the first time.
69+
Because the panel is a projection of state rather than a replay of `write_file` calls, an edit that rewrites an existing file shows up as one changed file here and as two entries in a tool call log.
10570

106-
## Next steps
71+
A second computed groups the flat list by directory, which is all the structure a tree needs.
72+
73+
<ExampleCode file="filesystem.component.ts" region="tree" title="filesystem.component.ts — grouping by directory" />
74+
75+
### The panel
76+
77+
The sidebar renders the grouped tree, a preview of the selected file, and the interrupt panel. A pending row carries `data-pending`, which dims and italicizes it; the badge is rendered from the same `pending` flag. `<chat-interrupt-panel>` is the same component every other LangGraph interrupt uses. There is no Deep Agents specific approval component, because there is no Deep Agents specific interrupt.
78+
79+
<ExampleCode file="filesystem.component.ts" region="workspace-panel" title="filesystem.component.ts — the workspace panel" />
80+
81+
Keeping the tree and the approval in one sidebar is the point of the layout: the reviewer reads the destination and the decision in the same glance.
82+
83+
### Resuming with a decision
84+
85+
`<chat-interrupt-panel>` emits an `InterruptAction` of `accept`, `edit`, `respond`, or `ignore`, and the component turns the two it handles into resume payloads. `HumanInTheLoopMiddleware` resumes on an object with a `decisions` list, one decision per paused tool call, each `{ "type": "approve" }`, `{ "type": "edit" }`, or `{ "type": "reject" }`.
86+
87+
<ExampleCode file="filesystem.component.ts" region="resume" title="filesystem.component.ts — resuming the run" />
88+
89+
The demo always sends exactly one decision, which is enough because only one write is ever paused here. The middleware rejects a resume whose decision count differs from the number of hanging tool calls, so a turn that batches two writes into one interrupt needs two decisions.
90+
91+
The consequence is visible in the tree. On Accept the write lands, the ghost row stops being pending, and the preview shows the real file content. On Ignore the interrupt clears without a file being written, so the row that only ever existed as a projection of the pending path disappears.
92+
93+
<Callout type="warning" title="The resume payload is an object, not a list">
94+
The middleware reads `interrupt(request)["decisions"]`, so a bare list or a bare string raises a `TypeError` on the server rather than a validation error the browser can show. The failure appears as a dead run rather than as a rejected submission, so the shape is worth getting right the first time.
95+
</Callout>
96+
97+
## Permission rules
98+
99+
A `FilesystemPermission` carries three fields: the `operations` it covers, the `paths` it matches, and the `mode` it applies. Rules are evaluated in declaration order and the first match wins; a call that matches no rule is allowed. Subagents inherit the parent rules unless they declare `permissions` of their own, which replaces the parent set entirely.
100+
101+
The three modes are `allow`, which lets the call proceed, `deny`, which returns a permission-denied error to the model, and `interrupt`, which pauses the call for human approval. Path patterns must start with `/` and may not contain `..`.
102+
103+
<Callout type="warning" title="Anchor the permission pattern">
104+
Give the pattern a literal prefix, as `/reports/**` does. Bulk tools such as `ls`, `glob`, and `grep` decide whether to fire the permission based on whether their search subtree could overlap the anchored prefix. A fully unanchored pattern collapses to the root and fires on every listing, which turns an approval gate into an interruption on each directory read.
105+
</Callout>
107106

108-
- [Planning](/docs/deep-agents/capabilities/planning) — the todo list the agent keeps while it files.
109-
- [Skills](/docs/deep-agents/capabilities/skills) — the same backend machinery, mounted read-only.
110-
- [Interrupts](/docs/langgraph/guides/interrupts) — the interrupt lifecycle underneath the approval.
111-
- [Chat interrupt panel](/docs/chat/components/chat-interrupt-panel) — the component that renders the approval.
107+
## What's Next
108+
109+
<CardGroup cols={2}>
110+
<Card title="Planning" href="/docs/deep-agents/capabilities/planning">
111+
The todo list the agent keeps while it files.
112+
</Card>
113+
<Card title="Skills" href="/docs/deep-agents/capabilities/skills">
114+
The same backend machinery, mounted read-only.
115+
</Card>
116+
<Card title="Interrupts" href="/docs/langgraph/guides/interrupts">
117+
The interrupt lifecycle underneath the approval.
118+
</Card>
119+
<Card title="Chat interrupt panel" href="/docs/chat/components/chat-interrupt-panel">
120+
The component that renders the approval.
121+
</Card>
122+
</CardGroup>

0 commit comments

Comments
 (0)