Phase 1 delivers the complete durability and storage layer:
- WAL with crash recovery: Append-only journal with BLAKE3 checksums,
fsync guarantees, and proper seek-to-EOF on reopen
- Storage engine: sled-backed KVStore with scan_prefix for range queries
- Content-addressed storage: H:{hash}, V:{hash}, E:{hash} key patterns
- Ingestor: Background worker tailing WAL, writing to KV with 8-byte
aligned record headers for rkyv zero-copy deserialization
- Comprehensive tests: 31 tests covering crash recovery, round-trips,
and multi-cycle durability
New crates: stemedb-wal, stemedb-storage, stemedb-ingest
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
14 KiB
Building a Research Document Generator with ADK-Go
This guide walks through building a multi-agent system that researches a topic and produces a comprehensive, well-structured document.
Overview
The research document generator uses a sequential pipeline of specialized agents:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Planner │───▶│ Researcher │───▶│ Writer │───▶│ Editor │
│ │ │ │ │ │ │ │
│ Creates │ │ Gathers │ │ Synthesizes │ │ Polishes │
│ outline │ │ sources │ │ into prose │ │ final doc │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
Project Setup
mkdir research-agent && cd research-agent
go mod init research-agent
go get google.golang.org/adk
Complete Implementation
package main
import (
"context"
"log"
"os"
"google.golang.org/adk/agent"
"google.golang.org/adk/agent/llmagent"
"google.golang.org/adk/agent/sequentialagent"
"google.golang.org/adk/cmd/launcher/adk"
"google.golang.org/adk/cmd/launcher/full"
"google.golang.org/adk/model/gemini"
"google.golang.org/adk/server/restapi/services"
"google.golang.org/adk/tool"
"google.golang.org/adk/tool/functiontool"
"google.golang.org/adk/tool/geminitool"
"google.golang.org/genai"
)
func main() {
ctx := context.Background()
// Initialize model
model, err := gemini.NewModel(ctx, "gemini-3-flash-preview", &genai.ClientConfig{
APIKey: os.Getenv("GOOGLE_API_KEY"),
})
if err != nil {
log.Fatalf("Failed to create model: %v", err)
}
// Build the research pipeline
pipeline, err := buildResearchPipeline(model)
if err != nil {
log.Fatalf("Failed to build pipeline: %v", err)
}
// Launch
l := full.NewLauncher()
cfg := &adk.Config{AgentLoader: services.NewSingleAgentLoader(pipeline)}
if err := l.Execute(ctx, cfg, os.Args[1:]); err != nil {
log.Fatalf("Execution failed: %v", err)
}
}
func buildResearchPipeline(model *gemini.Model) (agent.Agent, error) {
// Stage 1: Research Planner
planner, err := llmagent.New(llmagent.Config{
Name: "research_planner",
Model: model,
Description: "Creates a structured research plan and outline",
Instruction: `You are a research planner. Given a topic, create a comprehensive research plan.
Your output must include:
1. **Research Questions**: 5-7 key questions to investigate
2. **Outline**: Hierarchical document structure with sections and subsections
3. **Search Queries**: Specific search queries to find relevant information
4. **Source Types**: What kinds of sources to prioritize (academic, news, official, etc.)
Format your plan clearly with headers and bullet points.
Be thorough but focused on the most important aspects of the topic.`,
OutputKey: "research_plan",
})
if err != nil {
return nil, err
}
// Stage 2: Researcher (with web search)
researcher, err := llmagent.New(llmagent.Config{
Name: "researcher",
Model: model,
Description: "Gathers information from multiple sources",
Instruction: `You are a thorough researcher. Using the research plan below, gather comprehensive information.
RESEARCH PLAN:
{research_plan}
Your task:
1. Execute the search queries from the plan
2. Gather facts, statistics, quotes, and evidence
3. Note the source of each piece of information
4. Identify conflicting information or debates
5. Find recent developments and updates
For each section in the outline, compile relevant findings.
Organize your research notes by section.
Include direct quotes where impactful (with attribution).
Flag any gaps where information couldn't be found.`,
Tools: []tool.Tool{geminitool.GoogleSearch{}},
OutputKey: "research_notes",
})
if err != nil {
return nil, err
}
// Stage 3: Writer
writer, err := llmagent.New(llmagent.Config{
Name: "writer",
Model: model,
Description: "Synthesizes research into a cohesive document",
Instruction: `You are an expert writer. Transform the research into a polished document.
RESEARCH PLAN:
{research_plan}
RESEARCH NOTES:
{research_notes}
Writing guidelines:
1. Follow the outline structure from the research plan
2. Synthesize information into flowing prose (no bullet points in body)
3. Use topic sentences and smooth transitions
4. Integrate evidence naturally with proper attribution
5. Maintain objectivity; present multiple perspectives where relevant
6. Include an executive summary at the beginning
7. Add a references section at the end
Target length: Comprehensive but concise (aim for depth over breadth)
Tone: Professional, authoritative, accessible
Format: Use markdown with headers (## for main sections, ### for subsections)`,
OutputKey: "draft_document",
})
if err != nil {
return nil, err
}
// Stage 4: Editor
editor, err := llmagent.New(llmagent.Config{
Name: "editor",
Model: model,
Description: "Reviews and polishes the final document",
Instruction: `You are a meticulous editor. Review and improve the document.
DRAFT DOCUMENT:
{draft_document}
Editorial checklist:
1. **Clarity**: Simplify complex sentences; remove jargon or define it
2. **Flow**: Ensure logical progression; improve transitions
3. **Accuracy**: Flag any claims that seem unsupported
4. **Completeness**: Note any gaps in coverage
5. **Consistency**: Standardize formatting, terminology, tone
6. **Grammar**: Fix errors in spelling, punctuation, syntax
7. **Engagement**: Strengthen the opening; ensure compelling conclusion
Output the complete, polished document.
Add an "Editor's Note" section at the end with:
- Summary of major changes made
- Any remaining concerns or suggestions for future updates
- Confidence assessment (how well-supported is this document?)`,
OutputKey: "final_document",
})
if err != nil {
return nil, err
}
// Assemble pipeline
pipeline, err := sequentialagent.New(sequentialagent.Config{
AgentConfig: agent.Config{
Name: "research_document_generator",
Description: "Researches a topic and produces a comprehensive document",
SubAgents: []agent.Agent{planner, researcher, writer, editor},
},
})
if err != nil {
return nil, err
}
return pipeline, nil
}
Enhanced Version with Custom Tools
Add tools for saving drafts and managing sources:
// Source tracking tool
type AddSourceInput struct {
Title string `json:"title" jsonschema:"Source title"`
URL string `json:"url" jsonschema:"Source URL"`
Type string `json:"type" jsonschema:"Type: academic, news, official, blog"`
Quote string `json:"quote,omitempty" jsonschema:"Key quote from source"`
Date string `json:"date,omitempty" jsonschema:"Publication date"`
}
type AddSourceOutput struct {
SourceID int `json:"source_id"`
Message string `json:"message"`
}
var sourceCounter int
var sources []AddSourceInput
func addSource(ctx tool.Context, input AddSourceInput) AddSourceOutput {
sourceCounter++
sources = append(sources, input)
// Store in session state for other agents
state := ctx.Session().State()
state.Set("sources", sources)
return AddSourceOutput{
SourceID: sourceCounter,
Message: fmt.Sprintf("Added source #%d: %s", sourceCounter, input.Title),
}
}
// Document section tool
type SaveSectionInput struct {
Section string `json:"section" jsonschema:"Section name (e.g., 'Introduction')"`
Content string `json:"content" jsonschema:"Section content in markdown"`
}
type SaveSectionOutput struct {
Message string `json:"message"`
WordCount int `json:"word_count"`
TotalSections int `json:"total_sections"`
}
var documentSections = make(map[string]string)
func saveSection(ctx tool.Context, input SaveSectionInput) SaveSectionOutput {
documentSections[input.Section] = input.Content
wordCount := len(strings.Fields(input.Content))
// Persist to state
ctx.Session().State().Set("document_sections", documentSections)
return SaveSectionOutput{
Message: fmt.Sprintf("Saved section: %s", input.Section),
WordCount: wordCount,
TotalSections: len(documentSections),
}
}
// Create tools
func createResearchTools() ([]tool.Tool, error) {
sourceTool, err := functiontool.New(
functiontool.Config{
Name: "add_source",
Description: "Track a source used in research. Call this for every source you reference.",
},
addSource,
)
if err != nil {
return nil, err
}
sectionTool, err := functiontool.New(
functiontool.Config{
Name: "save_section",
Description: "Save a completed section of the document",
},
saveSection,
)
if err != nil {
return nil, err
}
return []tool.Tool{
geminitool.GoogleSearch{},
sourceTool,
sectionTool,
}, nil
}
Adding Parallel Research
For faster research, use parallel agents to investigate different aspects simultaneously:
import "google.golang.org/adk/agent/parallelagent"
func buildParallelResearcher(model *gemini.Model) (agent.Agent, error) {
// Background/history researcher
historyResearcher, _ := llmagent.New(llmagent.Config{
Name: "history_researcher",
Model: model,
Instruction: `Research the historical background and evolution of: {topic}
Focus on: origins, key milestones, how it developed over time.`,
Tools: []tool.Tool{geminitool.GoogleSearch{}},
OutputKey: "history_research",
})
// Current state researcher
currentResearcher, _ := llmagent.New(llmagent.Config{
Name: "current_researcher",
Model: model,
Instruction: `Research the current state and recent developments of: {topic}
Focus on: latest news, current statistics, recent changes.`,
Tools: []tool.Tool{geminitool.GoogleSearch{}},
OutputKey: "current_research",
})
// Expert opinions researcher
expertResearcher, _ := llmagent.New(llmagent.Config{
Name: "expert_researcher",
Model: model,
Instruction: `Research expert opinions and analysis on: {topic}
Focus on: thought leaders, academic perspectives, industry experts.`,
Tools: []tool.Tool{geminitool.GoogleSearch{}},
OutputKey: "expert_research",
})
// Future outlook researcher
futureResearcher, _ := llmagent.New(llmagent.Config{
Name: "future_researcher",
Model: model,
Instruction: `Research predictions and future outlook for: {topic}
Focus on: trends, forecasts, potential developments.`,
Tools: []tool.Tool{geminitool.GoogleSearch{}},
OutputKey: "future_research",
})
// Run all in parallel
parallel, err := parallelagent.New(parallelagent.Config{
AgentConfig: agent.Config{
Name: "parallel_research_team",
SubAgents: []agent.Agent{
historyResearcher,
currentResearcher,
expertResearcher,
futureResearcher,
},
},
})
return parallel, err
}
Then modify the main pipeline to use the parallel researcher:
// In buildResearchPipeline, replace single researcher with:
parallelResearcher, err := buildParallelResearcher(model)
if err != nil {
return nil, err
}
// Update writer instruction to use all research outputs:
writer, err := llmagent.New(llmagent.Config{
Instruction: `Synthesize all research into a cohesive document.
OUTLINE:
{research_plan}
HISTORICAL BACKGROUND:
{history_research}
CURRENT STATE:
{current_research}
EXPERT ANALYSIS:
{expert_research}
FUTURE OUTLOOK:
{future_research}
Create a comprehensive document that weaves all perspectives together.`,
// ...
})
Running the Agent
# Console mode - interactive
go run main.go
# Example prompts:
# > Write a research document on the impact of AI on healthcare
# > Create a comprehensive report on renewable energy trends in 2024
# > Research and document the history and future of quantum computing
# Web UI mode - for debugging and visualization
go run main.go web api webui
# Open http://localhost:8080
Output Example
For the prompt "Research the impact of remote work on urban planning":
# The Impact of Remote Work on Urban Planning
## Executive Summary
The rise of remote work, accelerated by the COVID-19 pandemic, is fundamentally
reshaping urban planning paradigms. This document examines...
## 1. Historical Context
### 1.1 Pre-Pandemic Work Patterns
Before 2020, remote work was limited to approximately 5% of the workforce...
### 1.2 The Pandemic Catalyst
The COVID-19 pandemic forced an unprecedented experiment in remote work...
## 2. Current Urban Impacts
### 2.1 Commercial Real Estate Transformation
Office vacancy rates in major cities have reached historic highs...
### 2.2 Residential Migration Patterns
Data from the U.S. Census Bureau shows significant population shifts...
## 3. Planning Responses
### 3.1 Zoning Adaptations
Cities are revising zoning codes to allow mixed-use developments...
## 4. Future Outlook
...
## References
1. Smith, J. (2024). "Remote Work and Urban Form." Journal of Planning...
2. ...
---
**Editor's Note**: This document synthesizes 23 sources spanning academic
research, government data, and industry reports. Confidence: High for
current trends; Medium for long-term predictions.
Best Practices
-
Be specific with prompts: "Research quantum computing" is too broad; "Research the current state of quantum error correction and its implications for practical quantum computers" is better.
-
Monitor intermediate outputs: Use the web UI to inspect
research_plan,research_notes, etc. to debug issues. -
Adjust pipeline stages: Some topics may need more research depth; others may need multiple editing passes.
-
Handle long documents: For very comprehensive documents, consider chunking the writing phase by section.
-
Add human review points: Insert a "reviewer" agent or callback that flags sections needing human verification.