Oxagen Docs

Document generation

How Oxagen agents author long-form documents through a vendor-neutral DocumentSpec — composing Google Docs from typed blocks, replacing placeholders, exporting PDF, and sharing with named users.

The document surface is the agent's long-form output channel. An agent composes a typed DocumentSpec — title, blocks, sharing intent — and the active provider materialises it as a Google Doc. The provider contract lives in packages/oxagen/oxagen/documents/; the agent never imports a vendor SDK and never sees raw OAuth credentials.

The DocumentSpec shape

A DocumentSpec is a Pydantic model in oxagen.documents.specs. Every spec is a sequence of typed DocumentBlock entries; the provider walks the block list and maps each kind onto the vendor's native element type.

Block kindMaps to (Google Docs)Used for
headingHeading 1 / 2 / 3 paragraph styleSection titles
paragraphPlain paragraph with optional inline runsBody text
bulleted_list / numbered_listList paragraph with bullet presetLists
tableNative table with header rowTabular data
imageInline image from a gs:// / https:// sourceDiagrams, screenshots
dividerHorizontal-rule paragraphSection breaks
placeholder{{name}} token, later filled by docs.replace_placeholdersTemplated briefs
page_breakPage breakMulti-page reports

Inline runs inside a paragraph carry bold / italic / underline / code / link annotations — no markdown round-tripping in the provider; the spec is the source of truth.

Capability surface

Every tool below is exposed on app, mcp, and api. The connection_kind="google_workspace" binding rejects calls in workspaces that have not connected Google with a structured 409 missing_connection response.

CapabilityCreditsWhat it does
docs.create_from_spec5Materialise a DocumentSpec as a new Google Doc. Returns (external_id, external_url, kind='document'). Writes the corresponding row to app.documents.
docs.append2Append a validated list of DocumentBlock entries to an existing document.
docs.replace_placeholders2Substitute {{name}} tokens with caller-supplied values. Used by the agent to expand a templated brief in one round-trip.
docs.export_pdf3Render the document to PDF and return the bytes. The PDF is also stored in app.documents with a parent_document_id pointing back at the source.
docs.share1Grant a list of email addresses access at the chosen role (reader / commenter / writer). Idempotent — repeating with the same role is a no-op.

Calling it

From an MCP client

{
  "tool": "docs.create_from_spec",
  "args": {
    "spec": {
      "title": "Acme account brief — Q2",
      "blocks": [
        { "kind": "heading", "level": 1, "text": "Acme account brief" },
        { "kind": "paragraph", "text": "Acme has been on the platform since 2024-09." },
        { "kind": "heading", "level": 2, "text": "Open issues" },
        { "kind": "bulleted_list", "items": [
            "ACME-235 — billing migration blocker",
            "ACME-451 — SSO rollout"
        ]}
      ],
      "share": { "emails": ["alice@acme.com"], "role": "commenter" }
    }
  }
}

The agent gets back the doc URL plus the canonical document id in app.documents. Subsequent docs.append, docs.replace_placeholders, or docs.export_pdf calls reference the document by id.

From the REST surface

curl -X POST https://api.oxagen.ai/v1/cap/docs.create_from_spec \
  -H "Authorization: Bearer $OXAGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d @brief.json

Idempotency-Key is the safe-retry contract: the second call returns the cached (external_id, external_url) instead of minting a duplicate doc.

Branding

When the workspace has a default BrandKit, the provider maps its palette / typography / spacing tokens onto Google Docs' named styles (Heading 1 / 2 / 3, Normal text, table header fill, divider colour) before the blocks are inserted. The kit's logo and footer markdown land in the doc header / footer. Generation without an active kit falls back to Google Docs defaults.

See Brand kits for the kit shape and how to manage them.

Storage and provenance

Every successful docs.create_from_spec writes a row to app.documents:

ColumnValue
kinduploaded
extension.gdoc
mime_typeapplication/vnd.google-apps.document
sourceagent_generated
providergoogle
external_idGoogle Drive file id
external_urlDrive viewer URL
generated_by_run_idThe agent run that produced the doc

The PDF emitted by docs.export_pdf lands as a sibling row with parent_document_id pointing at the source doc — same breadcrumb pattern used for connector-import revisions. See Artifact storage.

Permissions and credentials

The provider reads credentials from the workspace's Google Workspace connection. The agent never sees raw tokens; the provider acquires a short-lived access token at call time and discards it after the round-trip. Token rotation, scope, and revocation behaviour match the rest of the Google connectors.

A workspace without a connected Google account that calls a docs.* capability receives:

{
  "error": "missing_connection",
  "connection_kind": "google_workspace",
  "candidates": []
}

Audit

Every document generation writes an audit.event row keyed on the (tenant_id, workspace_id) chain with action='document.created', the DocumentSpec (truncated at 16 KB) in before, the external reference in after, and the per-row hash chained to the previous entry. The daily Merkle attestation job rolls the chain up to a single root and pins it to GCS object-lock. See Events, triggers, and audits.


Artifacts overview · Spreadsheets · Slides · PDF generation

On this page