概览

本 guide 演示如何使用 Deep Agents 从头构建 content writing agent。 你构建的 agent 将会:
  1. AGENTS.md 和 skill folders 加载 voice 与 workflow rules
  2. 使用 web_search 将 web research 委派给 specialized subagent
  3. 根据已加载 skill 草拟 blog 或 social content
  4. 使用 Gemini 生成 cover 或 social images,并将 files 保存到 project directory 下
本 tutorial 中的 code 会接入 image generation tools 和 filesystem backend,让 agent 可以在 project directory 下读写 posts、research notes 和 images。完整可运行 project 请参阅 content-builder-agent example。

Key concepts

本 tutorial 涵盖:

Prerequisites

API keys:
  • Anthropic(Claude)或其他 provider API key
  • Google(Gemini),用于通过 gemini-2.5-flash-image 进行 image generation
  • Tavily,用于 web search(free tier)
  • LangSmith,用于 tracing(可选)
Node.js 18 或更高版本。

Setup

1

创建 project directory

mkdir content-builder-agent
cd content-builder-agent
2

安装 dependencies

npm install deepagents @langchain/core @langchain/anthropic @google/generative-ai tavily zod tsx
添加 tsx 以运行 content_writer.ts--input-type=module flag 只适用于 --eval--print 或 stdin,不适用于 script file path。安装 @langchain/anthropic,让 LangChain 可以加载 createDeepAgent 使用的 default Claude model。
3

设置 API keys

export ANTHROPIC_API_KEY="your_anthropic_api_key"
export GOOGLE_API_KEY="your_google_api_key"
export TAVILY_API_KEY="your_tavily_api_key"           # Optional
export LANGSMITH_API_KEY="your_langsmith_api_key"     # Optional

添加 configuration files

该 example 将 behavior 保存在三类 files 中:memory、skills 和 subagent definitions。
1

Add AGENTS.md

在 project root 中创建 AGENTS.md。 稍后创建 agent 并将此 file 指定为 memory parameter 的一部分时,它会加载到 system prompt 中,让 brand voice 和 research expectations 应用于每次 run。
# Content Writer Agent

You are a content writer for a technology company. Your job is to create engaging, informative content that educates readers about AI, software development, and emerging technologies.

## Brand Voice

- **Professional but approachable**: Write like a knowledgeable colleague, not a textbook
- **Clear and direct**: Avoid jargon unless necessary; explain technical concepts simply
- **Confident but not arrogant**: Share expertise without being condescending
- **Engaging**: Use concrete examples, analogies, and stories to illustrate points

## Writing Standards

1. **Use active voice**: "The agent processes requests" not "Requests are processed by the agent"
2. **Lead with value**: Start with what matters to the reader, not background
3. **One idea per paragraph**: Keep paragraphs focused and scannable
4. **Concrete over abstract**: Use specific examples, numbers, and case studies
5. **End with action**: Every piece should leave the reader knowing what to do next

## Content Pillars

Our content focuses on:
- AI agents and automation
- Developer tools and productivity
- Software architecture and best practices
- Emerging technologies and trends

## Formatting Guidelines

- Use headers (H2, H3) to break up long content
- Include code examples where relevant (with syntax highlighting)
- Add bullet points for lists of 3+ items
- Keep sentences under 25 words when possible
- Include a clear call-to-action at the end

## Research Requirements

Before writing on any topic:
1. Use the `researcher` subagent for in-depth topic research
2. Gather at least 3 credible sources
3. Identify the key points readers need to understand
4. Find concrete examples or case studies to illustrate concepts
若要让此 agent 遵守你自己的 tone、pillars 和 formatting rules,请更新 AGENTS.md 中的文本。
2

Add skills

创建 skills/ directory。每个 skill 都是一个 folder,其中包含带 YAML frontmatter(namedescription)和 skill instructions 的 SKILL.md file。创建 skills/blog-post/SKILL.md,并将以下 text 复制进去。它包含创建 long-form posts、为 SEO 优化 content,以及生成 cover images 的信息。
---
name: blog-post
description: Writes and structures long-form blog posts, creates tutorial outlines, and optimizes content for SEO with cover image generation. Use when the user asks to write a blog post, article, how-to guide, tutorial, technical writeup, thought leadership piece, or long-form content.
---

# Blog Post Writing Skill

## Research First (Required)

**Before writing any blog post, you MUST delegate research:**

1. Use the `task` tool with `subagent_type: "researcher"`
2. In the description, specify BOTH the topic AND where to save:

```
task(
    subagent_type="researcher",
    description="Research [TOPIC]. Save findings to research/[slug].md"
)
```

Example:
```
task(
    subagent_type="researcher",
    description="Research the current state of AI agents in 2025. Save findings to research/ai-agents-2025.md"
)
```

3. After research completes, read the findings file before writing

## Output Structure (Required)

**Every blog post MUST have both a post AND a cover image:**

```
blogs/
└── <slug>/
    ├── post.md        # The blog post content
    └── hero.png       # REQUIRED: Generated cover image
```

Example: A post about "AI Agents in 2025" → `blogs/ai-agents-2025/`

**You MUST complete both steps:**
1. Write the post to `blogs/<slug>/post.md`
2. Generate a cover image using `generate_image` and save to `blogs/<slug>/hero.png`

**A blog post is NOT complete without its cover image.**

## Blog Post Structure

Every blog post should follow this structure:

### 1. Hook (Opening)
- Start with a compelling question, statistic, or statement
- Make the reader want to continue
- Keep it to 2-3 sentences

### 2. Context (The Problem)
- Explain why this topic matters
- Describe the problem or opportunity
- Connect to the reader's experience

### 3. Main Content (The Solution)
- Break into 3-5 main sections with H2 headers
- Each section covers one key point
- Include code examples, diagrams, or screenshots where helpful
- Use bullet points for lists

### 4. Practical Application
- Show how to apply the concepts
- Include step-by-step instructions if applicable
- Provide code snippets or templates

### 5. Conclusion & CTA
- Summarize key takeaways (3 bullets max)
- End with a clear call-to-action
- Link to related resources

## Cover Image Generation

After writing the post, generate a cover image using the `generate_cover` tool:

```
generate_cover(prompt="A detailed description of the image...", slug="your-blog-slug")
```

The tool saves the image to `blogs/<slug>/hero.png`.

### Writing Effective Image Prompts

Structure your prompt with these elements:

1. **Subject**: What is the main focus? Be specific and concrete.
2. **Style**: Art direction (minimalist, isometric, flat design, 3D render, watercolor, etc.)
3. **Composition**: How elements are arranged (centered, rule of thirds, symmetrical)
4. **Color palette**: Specific colors or mood (warm earth tones, cool blues and purples, high contrast)
5. **Lighting/Atmosphere**: Soft diffused light, dramatic shadows, golden hour, neon glow
6. **Technical details**: Aspect ratio considerations, negative space for text overlay

### Example Prompts

**For a technical blog post:**
```
Isometric 3D illustration of interconnected glowing cubes representing AI agents, each cube has subtle circuit patterns. Cubes connected by luminous data streams. Deep navy background (#0a192f) with electric blue (#64ffda) and soft purple (#c792ea) accents. Clean minimal style, lots of negative space at top for title. Professional tech aesthetic.
```

**For a tutorial/how-to:**
```
Clean flat illustration of hands typing on a keyboard with abstract code symbols floating upward, transforming into lightbulbs and gears. Warm gradient background from soft coral to light peach. Friendly, approachable style. Centered composition with space for text overlay.
```

**For thought leadership:**
```
Abstract visualization of a human silhouette profile merging with geometric neural network patterns. Split composition - organic watercolor texture on left transitioning to clean vector lines on right. Muted sage green and warm terracotta color scheme. Contemplative, forward-thinking mood.
```

## SEO Considerations

- Include the main keyword in the title and first paragraph
- Use the keyword naturally 3-5 times throughout
- Keep the title under 60 characters
- Write a meta description (150-160 characters)

## Quality Checklist

Before finishing:
- [ ] Post saved to `blogs/<slug>/post.md`
- [ ] Hero image generated at `blogs/<slug>/hero.png`
- [ ] Hook grabs attention in first 2 sentences
- [ ] Each section has a clear purpose
- [ ] Conclusion summarizes key points
- [ ] CTA tells reader what to do next
接下来,创建 skills/social-media/SKILL.md,并将以下 text 复制进去。它包含草拟 social media posts 和生成配套 imagery 的信息:
---
name: social-media
description: Drafts engaging social media posts, writes hooks, suggests hashtags, creates thread structures, and generates companion images. Use when the user asks to write a LinkedIn post, tweet, Twitter/X thread, social media caption, social post, or repurpose content for social platforms.
---

# Social Media Content Skill

## Research First (Required)

**Before writing any social media content, you MUST delegate research:**

1. Use the `task` tool with `subagent_type: "researcher"`
2. In the description, specify BOTH the topic AND where to save:

```
task(
    subagent_type="researcher",
    description="Research [TOPIC]. Save findings to research/[slug].md"
)
```

Example:
```
task(
    subagent_type="researcher",
    description="Research renewable energy trends in 2025. Save findings to research/renewable-energy.md"
)
```

3. After research completes, read the findings file before writing

## Output Structure (Required)

**Every social media post MUST have both content AND an image:**

**LinkedIn posts:**
```
linkedin/
└── <slug>/
    ├── post.md        # The post content
    └── image.png      # REQUIRED: Generated visual
```

**Twitter/X threads:**
```
tweets/
└── <slug>/
    ├── thread.md      # The thread content
    └── image.png      # REQUIRED: Generated visual
```

Example: A LinkedIn post about "prompt engineering" → `linkedin/prompt-engineering/`

**You MUST complete both steps:**
1. Write the content to the appropriate path
2. Generate an image using `generate_image` and save alongside the post

**A social media post is NOT complete without its image.**

## Platform Guidelines

### LinkedIn

**Format:**
- 1,300 character limit (show more after ~210 chars)
- First line is crucial - make it hook
- Use line breaks for readability
- 3-5 hashtags at the end

**Tone:**
- Professional but personal
- Share insights and learnings
- Ask questions to drive engagement
- Use "I" and share experiences

**Structure:**
```
[Hook - 1 compelling line]

[Empty line]

[Context - why this matters]

[Empty line]

[Main insight - 2-3 short paragraphs]

[Empty line]

[Call to action or question]

#hashtag1 #hashtag2 #hashtag3
```

### Twitter/X

**Format:**
- 280 character limit per tweet
- Threads for longer content (use 1/🧵 format)
- No more than 2 hashtags per tweet

**Thread Structure:**
```
1/🧵 [Hook - the main insight]

2/ [Supporting point 1]

3/ [Supporting point 2]

4/ [Example or evidence]

5/ [Conclusion + CTA]
```

## Image Generation

Every social media post needs an eye-catching image. Use the `generate_social_image` tool:

```
generate_social_image(prompt="A detailed description...", platform="linkedin", slug="your-post-slug")
```

The tool saves the image to `<platform>/<slug>/image.png`.

### Social Image Best Practices

Social images need to work at small sizes in crowded feeds:
- **Bold, simple compositions** - one clear focal point
- **High contrast** - stands out when scrolling
- **No text in image** - too small to read, platforms add their own
- **Square or 4:5 ratio** - works across platforms

### Writing Effective Prompts

Include these elements:

1. **Single focal point**: One clear subject, not a busy scene
2. **Bold style**: Vibrant colors, strong shapes, high contrast
3. **Simple background**: Solid color, gradient, or subtle texture
4. **Mood/energy**: Match the post tone (inspiring, urgent, thoughtful)

### Example Prompts

**For an insight/tip post:**
```
Single glowing lightbulb floating against a deep purple gradient background, lightbulb made of interconnected golden geometric lines, rays of soft light emanating outward. Minimal, striking, high contrast. Square composition.
```

**For announcements/news:**
```
Abstract rocket ship made of colorful geometric shapes launching upward with a trail of particles. Bright coral and teal color scheme against clean white background. Energetic, celebratory mood. Bold flat illustration style.
```

**For thought-provoking content:**
```
Two overlapping translucent circles, one blue one orange, creating a glowing intersection in the center. Represents collaboration or intersection of ideas. Dark charcoal background, soft ethereal glow. Minimalist and contemplative.
```

## Content Types

### Announcement Posts
- Lead with the news
- Explain the impact
- Include link or next step

### Insight Posts
- Share one specific learning
- Explain the context briefly
- Make it actionable

### Question Posts
- Ask a genuine question
- Provide your take first
- Keep it focused on one topic

## Quality Checklist

Before finishing:
- [ ] Post saved to `linkedin/<slug>/post.md` or `tweets/<slug>/thread.md`
- [ ] Image generated alongside the post
- [ ] First line hooks attention
- [ ] Content fits platform limits
- [ ] Tone matches platform norms
- [ ] Has clear CTA or question
- [ ] Hashtags are relevant (not generic)
这些 instructions 会要求 agent 先调用 researcher subagent,在 blogs/linkedin/tweets/ 下写入 markdown,并调用 generate_covergenerate_social_image 生成 images。稍后创建 agent 并指定 skills folder(s) 时,这些 skill folders 中 SKILLS.md files 的 frontmatter 会加载到 system prompt 中,因此当 task 匹配 skill description 时,agent 可以使用该 skill。

构建 script

在 project root 中创建 content_writer.ts。以下 sections 按顺序放入同一个 file。
1

添加 tools

Researcher 使用 Tavily search。Blog 和 social workflows 使用 Google Generative AI SDK 进行 image generation。
import { tool } from "@langchain/core/tools";
import * as z from "zod";
import * as fs from "node:fs";
import * as path from "node:path";

const EXAMPLE_DIR = path.dirname(new URL(import.meta.url).pathname);

const webSearch = tool(
  async ({ query, maxResults = 5, topic = "general" }) => {
    const apiKey = process.env.TAVILY_API_KEY;
    if (!apiKey) return { error: "TAVILY_API_KEY not set" };
    try {
      const { TavilyClient } = await import("tavily");
      const client = new TavilyClient({ apiKey });
      return client.search(query, { maxResults, topic });
    } catch (e) {
      return { error: `Search failed: ${e}` };
    }
  },
  {
    name: "web_search",
    description: "Search the web for current information.",
    schema: z.object({
      query: z.string().describe("The search query (be specific and detailed)"),
      maxResults: z
        .number()
        .optional()
        .describe("Number of results to return (default: 5)"),
      topic: z
        .enum(["general", "news"])
        .optional()
        .describe('"general" for most queries, "news" for current events'),
    }),
  },
);

const generateCover = tool(
  async ({ prompt, slug }) => {
    try {
      const { GoogleGenerativeAI } = await import("@google/generative-ai");
      const genai = new GoogleGenerativeAI(process.env.GOOGLE_API_KEY ?? "");
      const model = genai.getGenerativeModel({
        model: "gemini-2.5-flash-image",
      });
      const result = await model.generateContent(prompt);
      const part = result.response.candidates?.[0]?.content?.parts?.find(
        (p) => p.inlineData,
      );
      if (!part?.inlineData) return "No image generated";
      const outputPath = path.join(EXAMPLE_DIR, "blogs", slug, "hero.png");
      fs.mkdirSync(path.dirname(outputPath), { recursive: true });
      fs.writeFileSync(outputPath, Buffer.from(part.inlineData.data, "base64"));
      return `Image saved to ${outputPath}`;
    } catch (e) {
      return `Error: ${e}`;
    }
  },
  {
    name: "generate_cover",
    description: "Generate a cover image for a blog post.",
    schema: z.object({
      prompt: z
        .string()
        .describe("Detailed description of the image to generate."),
      slug: z
        .string()
        .describe("Blog post slug. Image saves to blogs/<slug>/hero.png"),
    }),
  },
);

const generateSocialImage = tool(
  async ({ prompt, platform, slug }) => {
    try {
      const { GoogleGenerativeAI } = await import("@google/generative-ai");
      const genai = new GoogleGenerativeAI(process.env.GOOGLE_API_KEY ?? "");
      const model = genai.getGenerativeModel({
        model: "gemini-2.5-flash-image",
      });
      const result = await model.generateContent(prompt);
      const part = result.response.candidates?.[0]?.content?.parts?.find(
        (p) => p.inlineData,
      );
      if (!part?.inlineData) return "No image generated";
      const outputPath = path.join(EXAMPLE_DIR, platform, slug, "image.png");
      fs.mkdirSync(path.dirname(outputPath), { recursive: true });
      fs.writeFileSync(outputPath, Buffer.from(part.inlineData.data, "base64"));
      return `Image saved to ${outputPath}`;
    } catch (e) {
      return `Error: ${e}`;
    }
  },
  {
    name: "generate_social_image",
    description: "Generate an image for a social media post.",
    schema: z.object({
      prompt: z
        .string()
        .describe("Detailed description of the image to generate."),
      platform: z.string().describe('Either "linkedin" or "tweets"'),
      slug: z
        .string()
        .describe("Post slug. Image saves to <platform>/<slug>/image.png"),
    }),
  },
);
2

创建 agent

使用 createDeepAgent 创建 deep agent 时,传入 memory paths、skills directory、image tools、inline subagent definition,以及以 example directory 为 root 的 FilesystemBackend,以便 ./AGENTS.md./skills/ 等 paths 能正确解析。
import { createDeepAgent, FilesystemBackend } from "deepagents";

function createContentWriter() {
  const researcherSubagent = {
    name: "researcher",
    description:
      "Research subagent with web search capability. Delegate research tasks here.",
    systemPrompt:
      "You are a research assistant. Use the web_search tool to find current, accurate information and return well-organized findings.",
    tools: [webSearch],
  };

  return createDeepAgent({
    model: "google-genai:gemini-3.5-flash",
    memory: ["./AGENTS.md"],
    skills: ["./skills/"],
    tools: [generateCover, generateSocialImage],
    subagents: [researcherSubagent],
    backend: new FilesystemBackend({ rootDir: EXAMPLE_DIR }),
  });
}
3

添加 entry point

const task =
  process.argv.slice(2).join(" ") ||
  "Write a blog post about how AI agents are transforming software development";

const agent = createContentWriter();
const result = await agent.invoke({
  messages: [{ role: "user", content: task }],
  config: { configurable: { threadId: "content-builder-demo" } },
});

const messages = result.messages ?? [];
for (const msg of messages) {
  if (msg.content) console.log(msg.content);
}

Run the agent

Filesystem backend 可以读取、写入和删除 root_dir 下的 files。请只在 dedicated directory 中运行,并在发布前 review generated content。
从 project directory 运行:
npx tsx content_writer.ts
将 prompt 作为 extra arguments 传入:
npx tsx content_writer.ts Write a blog post about prompt engineering
设置 LANGSMITH_API_KEY 后,你可以在 LangSmith 中 inspect runs。

Output

成功时,agent 会在 project root(example directory)下写入 artifacts,例如:
blogs/
└── prompt-engineering/
    ├── post.md
    └── hero.png
research/
└── prompt-engineering.md
Paths 遵循 SKILL.md 中的 skill instructions。

完整代码

在 GitHub 上浏览完整 content-builder-agent example,其中包括 Rich-based streaming UI。

下一步

  • 编辑 AGENTS.md 以更改 brand voice 和 research requirements
  • skills/<name>/SKILL.md 下为新的 content types 添加 skills
  • subagents.yaml 中添加 subagents,并在 load_subagents 中注册 tools
  • 阅读 SubagentsSkillsCustomization 了解更深入配置