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:
- Bucket — create or select a bucket to hold the document.
- Remote ingest — submit the document by URL; save the returned
processId. - Poll — check processing status until it reaches
complete. - Search — query the bucket with
search_content. - Answer — pass
search.textto an LLM as context to generate the final 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.
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:
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:
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:
The status moves through queued → training → processing → complete, 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.
Search
With the document at complete, query the bucket:
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: injectsearch.textinto 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.textis a flattened string, not structured per-chunk data, so don’t try to reconstruct that UI from it.
A minimal answer-generation pattern:
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()withDocument(...). It detects a localfile_pathautomatically, uploads it through the pre-signed upload service, and submits it via the same remote-ingest mechanismdocument_ingestremoteuses under the hood. -
Direct REST — request a pre-signed upload URL,
PUTthe file to it, then submit the resulting hosted URL toPOST /v1/ingest/documents/remotewith anX-API-Keyheader (notAuthorization: Bearer):
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:
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
- Need to extract structured JSON instead of a grounded answer? See Extract Data With an Agent.
- Something not behaving as expected? See Verify and Troubleshoot.

