Your ADK agent just synthesized a competitive analysis from 15 sources. It's sitting in your terminal. Now what — copy-paste it into a Google Doc? Slack it as a wall of text? Here's what that looks like with SurfaceDocs:
def publish_report(title: str, content: str) -> dict:
"""Publish a document to SurfaceDocs and return the shareable URL."""
from surfacedocs import SurfaceDocs
client = SurfaceDocs()
result = client.save(content)
return {"url": result.url, "id": result.id}
That's an ADK tool. A Python function. The agent calls it, gets back a URL, and the output exists somewhere permanent. Let's build the full thing.
The Problem: Agent Output Is Ephemeral
If you've built anything with ADK, you've hit this wall. The agent does impressive work — research, analysis, code generation, report writing — and then the output just... evaporates.
It lives in:
- Terminal logs that scroll away
- Session state that gets garbage collected
- JSON blobs that nobody wants to read
None of these are shareable. None of them look professional. And none of them persist in a way that's useful to anyone who wasn't staring at the terminal when the agent ran.
The typical fix is building custom output infrastructure. A React app, a Notion integration, some unholy Apps Script bridge. You end up spending more time on the output pipeline than on the agent itself.
SurfaceDocs exists specifically for this — it's an API-first document host built for machine-generated content. Think S3 for AI documents, but with a built-in viewer. Your agent publishes structured content, SurfaceDocs gives you a shareable URL with professional rendering. No frontend to build, no formatting to wrangle.
Getting Started
pip install google-adk surfacedocs
Set your environment variables:
export GOOGLE_API_KEY="your-gemini-api-key"
export SURFACEDOCS_API_KEY="sd_live_..."
Optionally, set a default folder to organize agent outputs:
export SURFACEDOCS_FOLDER_ID="your-folder-id"
That's the setup. No servers, no databases, no infrastructure.
Building the SurfaceDocs Tool
ADK tools are Python functions. The agent sees the docstring and parameter types, decides when to call them, and uses the return value. Here's a SurfaceDocs publishing tool:
from surfacedocs import SurfaceDocs, DOCUMENT_SCHEMA, SYSTEM_PROMPT
docs_client = SurfaceDocs() # picks up SURFACEDOCS_API_KEY from env
def publish_document(content: str, folder_id: str = "") -> dict:
"""Publish a structured document to SurfaceDocs.
Use this tool when you've completed research, analysis, or any work product
that should be saved as a shareable document. Format the content as a JSON
string matching the SurfaceDocs document schema.
Args:
content: JSON string with document title and blocks following the
SurfaceDocs schema.
folder_id: Optional folder ID to organize the document.
Returns:
Dictionary with the shareable URL and document ID.
"""
kwargs = {}
if folder_id:
kwargs["folder_id"] = folder_id
result = docs_client.save(content, **kwargs)
return {
"url": result.url,
"document_id": result.id,
"status": "published"
}
The key detail: SurfaceDocs provides DOCUMENT_SCHEMA and SYSTEM_PROMPT constants that tell the LLM exactly how to structure its output. You feed these into the agent's instructions so Gemini generates content the SDK can consume directly.
The Full Agent: A Research Publisher
Here's a complete, runnable example — a research agent that investigates a topic and publishes its findings as a formatted document:
from google.adk.agents import LlmAgent
from surfacedocs import SurfaceDocs, DOCUMENT_SCHEMA, SYSTEM_PROMPT
# Initialize SurfaceDocs client
docs_client = SurfaceDocs()
def publish_document(content: str, folder_id: str = "") -> dict:
"""Publish a structured document to SurfaceDocs.
Call this tool when your research is complete and you're ready to
publish. Format the content as a JSON string with a title and blocks
following the SurfaceDocs document schema provided in your instructions.
Args:
content: JSON string with document title and content blocks.
folder_id: Optional folder ID for organization.
Returns:
Dictionary with the shareable URL and document ID.
"""
kwargs = {}
if folder_id:
kwargs["folder_id"] = folder_id
result = docs_client.save(content, **kwargs)
return {"url": result.url, "document_id": result.id}
research_agent = LlmAgent(
name="research_publisher",
model="gemini-2.5-flash",
instruction=f"""You are a research analyst. When given a topic, produce a
thorough, well-structured report and publish it using the publish_document tool.
Your report should include:
- An executive summary
- Key findings with supporting detail
- A conclusion with recommendations
When you're ready to publish, format your document as a JSON string
following this schema:
{DOCUMENT_SCHEMA}
{SYSTEM_PROMPT}
After publishing, share the URL with the user.""",
tools=[publish_document],
)
Run it with the ADK runner or CLI:
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
session_service = InMemorySessionService()
runner = Runner(
agent=research_agent,
app_name="research_app",
session_service=session_service,
)
When the agent finishes its research, it calls publish_document with structured JSON content. SurfaceDocs renders it — headings, code blocks, tables, callouts, the works — and returns a URL like https://app.surfacedocs.dev/d/abc123.
The agent gets that URL back and can share it in its response. The user clicks the link and sees a clean, formatted document. Not a JSON blob. Not a terminal dump. A real document.
What You Get
The published document supports all the block types you'd expect:
- Headings at multiple levels
- Code blocks with syntax highlighting
- Tables for structured data
- Callouts for warnings, tips, and key takeaways
- Mermaid diagrams for architecture and flow charts
- Lists, blockquotes, images, dividers
Everything renders in a clean viewer at a stable URL. No expiration, no login wall. Send the link to your team, your client, your Slack channel — it just works.
If you set a folder_id, documents are organized together. Run the agent daily and you build up a library of reports, all browsable in one place.
Multi-Agent Systems: Every Agent Gets an Output Layer
This gets more interesting with ADK's sub_agents pattern. In a multi-agent system, each agent can publish its own work products independently:
from google.adk.agents import LlmAgent
market_analyst = LlmAgent(
name="market_analyst",
model="gemini-2.5-flash",
instruction="Analyze market trends and publish your report...",
tools=[publish_document],
)
technical_reviewer = LlmAgent(
name="technical_reviewer",
model="gemini-2.5-flash",
instruction="Review technical architecture and publish findings...",
tools=[publish_document],
)
coordinator = LlmAgent(
name="coordinator",
model="gemini-2.5-flash",
instruction="""Coordinate the research team. Delegate market analysis
to market_analyst and technical review to technical_reviewer.
Each will publish their own reports and return URLs.""",
sub_agents=[market_analyst, technical_reviewer],
)
The coordinator delegates. Each sub-agent does its work and publishes to SurfaceDocs. The coordinator collects the URLs and presents them. You end up with multiple professional documents from a single prompt — each with its own shareable link, each organized in your folder.
This is where the "output layer" concept clicks. You're not building per-agent output handling. You're not stitching together markdown files. Every agent in the system has the same publishing primitive, and the output is instantly accessible to anyone with the link.
Why This Matters
The gap between "agent did something useful" and "someone can use what the agent did" is where most agent projects stall. You build an impressive demo, the agent generates great analysis, and then you spend a week building a way to get that analysis in front of people.
SurfaceDocs collapses that gap to a function call. The agent's output is a first-class artifact with a URL the moment it's generated. No infrastructure to maintain, no frontend to build, no formatting pipeline to debug.
For ADK specifically, the fit is clean — tools are just Python functions, SurfaceDocs is a Python SDK, and the integration is a few lines of code wiring them together. The agent doesn't need to know about document hosting. It just calls a tool and gets a URL back.
If you're building ADK agents that generate anything worth reading twice, give them somewhere to put it.
Links
- SurfaceDocs: surfacedocs.dev
- SurfaceDocs Python SDK: pypi.org/project/surfacedocs
- Google ADK Docs: google.github.io/adk-docs
Top comments (0)