Ingest, Search, And Answer With An Agent

This page covers the main GroundX Agent Harness workflow: get a document into GroundX, wait for it to finish processing, search it, and use the result to answer a question. It uses the hosted MCP tools documented in Connect Hosted MCP Tools — every tool name here matches that page’s 12-tool default surface exactly. If you haven’t connected MCP yet, start there (or Install GroundX Agent Harness) first.

Agent Harness is the promoted path for agent-driven use of GroundX, not a replacement for the direct SDK/API path. If MCP is unavailable, or you’re integrating from application code rather than an MCP client, use the Direct SDK/API Quickstart instead. The underlying GroundX operations are the same either way.

Workflow

Five steps, end to end:

  1. Bucket — create or select a bucket to hold the document.
  2. Remote ingest — submit the document by URL; save the returned processId.
  3. Poll — check processing status until it reaches complete.
  4. Search — query the bucket with search_content.
  5. Answer — pass search.text to an LLM as context to generate the final answer.
bucket_create (or bucket_list)
document_ingestremote → processId
document_getprocessingstatusbyid … poll until status = complete …
search_content → search.text, search.results
LLM(search.text + user question) → answer

Ingest is asynchronous at every step — don’t assume a document is searchable right after the ingest call returns.

This workflow requires a key with groundx:ingest and groundx:write scopes; a read-only key cannot complete it. See Scope Requirements below.

Create Or Select A Bucket

A bucket holds the documents you ingest and search. Use bucket_list to see what already exists, or bucket_create to make a new one.

Ask the agent: "List my GroundX buckets."

This calls bucket_list, which takes no required arguments and returns an array of buckets (bucketId, name, fileCount, and related fields).

If none of the existing buckets fit, create one:

Ask the agent: "Create a GroundX bucket called my-project."

This calls bucket_create with a name. Save the bucketId from the response — every later step needs it.

Ingest A Remote Document

With a bucketId, ingest a document that’s reachable by public URL:

Ask the agent: "Ingest https://example.com/report.pdf into bucket 1234."

This calls document_ingestremote with the bucket ID and the document’s sourceUrl (plus optional metadata such as fileName). The call returns immediately with a processId — the document is not yet searchable. Save the processId; you need it for the next step.

Poll Until Complete

Ingest is asynchronous. Before searching, poll the document’s status with document_getprocessingstatusbyid using the processId from the previous step:

Ask the agent: "Check the ingest status for process abc123."

The status moves through queuedtrainingprocessingcomplete, error, or cancelled. Poll every few seconds for a single document, and back off to roughly every 30 seconds for large batches. Keep polling until the status is one of:

  • complete — the document is indexed and searchable. Proceed to search.
  • error — inspect the response for details before retrying.
  • cancelled — the ingest was cancelled; it will not become searchable.

Do not call search until the status is complete. The document isn’t indexed before then, so search won’t return its content.

With the document at complete, query the bucket:

Ask the agent: "Search bucket 1234 for the Q3 revenue figures."

This calls search_content with the bucket (or group) id and a natural-language query. Use it as the standard search path for a bucket or group.

If you already know the exact document IDs to search (for example, from a prior listing call), use search_documents with documentIds and a query to scope the query to just those documents. Don’t use search_documents as a general bucket-wide search shortcut — use search_content for that.

Answer With Search Text

search_content and search_documents both return two things, for different uses:

  • search.text — a single, pre-assembled string combining the retrieved chunks with their contextual metadata, ready to drop directly into an LLM prompt. This is the recommended way to generate an answer: inject search.text into the system prompt, then send the user’s question as the user message.
  • search.results — an array of individual chunk objects (score, documentId, sourceUrl, page images, bounding boxes, and any attached metadata). Use this for structured, per-chunk data — for example, to render citations, highlight the exact source passage, or build a source-attribution UI. search.text is a flattened string, not structured per-chunk data, so don’t try to reconstruct that UI from it.

A minimal answer-generation pattern:

system:
You are a knowledgeable assistant. Answer using only the information below.
If the answer isn't in the provided information, say so — do not fabricate one.
===
{search.text}
===
user:
{the user's question}

For citations, pull sourceUrl / fileName and the chunk’s location data from the matching entries in search.results and display them alongside the generated answer.

Local File Fallback

MCP has no tool for uploading a local file directly — document_ingestremote only accepts documents reachable at a public URL, and it’s the only ingest tool in the 12-tool default MCP surface. MCP alone cannot ingest a document on a local filesystem.

For local files, fall back to the direct SDK/API path:

  • Python SDK — use client.ingest() with Document(...). It detects a local file_path automatically, uploads it through the pre-signed upload service, and submits it via the same remote-ingest mechanism document_ingestremote uses under the hood.

    1from groundx import Document, GroundX
    2
    3client = GroundX(api_key="YOUR_API_KEY")
    4
    5response = client.ingest(
    6 documents=[
    7 Document(
    8 bucket_id=bucket_id, # the bucketId saved from the "Create Or Select A Bucket" step
    9 file_path="/local/path/report.pdf",
    10 file_name="report.pdf",
    11 file_type="pdf",
    12 ),
    13 ]
    14)
    15process_id = response.ingest.process_id
  • Direct REST — request a pre-signed upload URL, PUT the file to it, then submit the resulting hosted URL to POST /v1/ingest/documents/remote with an X-API-Key header (not Authorization: Bearer):

    1POST /v1/ingest/documents/remote
    2X-API-Key: YOUR_API_KEY
    3Content-Type: application/json
    4
    5{
    6 "documents": [
    7 {
    8 "bucketId": 1234,
    9 "sourceUrl": "{hosted URL from the pre-signed upload}",
    10 "fileName": "report.pdf",
    11 "fileType": "pdf"
    12 }
    13 ]
    14}

Once the hosted URL is ingested this way, the rest of the workflow (poll, search, answer) is identical to the remote-document path above, through either MCP or SDK/REST.

Scope Requirements

This workflow needs more than read access. Per the derived-scope rule in Connect Hosted MCP Tools — Scope And Visibility:

StepToolRequired scope
Create bucketbucket_creategroundx:write
List bucketsbucket_listgroundx:read
Ingest documentdocument_ingestremotegroundx:ingest
Poll statusdocument_getprocessingstatusbyidgroundx:ingest
Searchsearch_content / search_documentsgroundx:write

A read-only (groundx:read-only) key cannot complete this workflow. As documented in Connect Hosted MCP Tools, a read-only session only ever sees bucket_list, group_list, and health_get from the 12 default tools — document_ingestremote, document_getprocessingstatusbyid, search_content, and search_documents all require groundx:ingest or groundx:write and won’t be visible. To run this workflow, connect with a key granted both groundx:ingest and groundx:write scopes.

Next Steps