DEV Community

SurfaceDocs
SurfaceDocs

Posted on

SurfaceDocs + Gemini ADK: Agent Output That Sticks Around

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}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Set your environment variables:

export GOOGLE_API_KEY="your-gemini-api-key"
export SURFACEDOCS_API_KEY="sd_live_..."
Enter fullscreen mode Exit fullscreen mode

Optionally, set a default folder to organize agent outputs:

export SURFACEDOCS_FOLDER_ID="your-folder-id"
Enter fullscreen mode Exit fullscreen mode

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"
    }
Enter fullscreen mode Exit fullscreen mode

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],
)
Enter fullscreen mode Exit fullscreen mode

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,
)
Enter fullscreen mode Exit fullscreen mode

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],
)
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)