list_documents and
read_document; no parser image, environment variable, execution backend,
third-party account or per-agent opt-in is required. Files shared earlier in the
conversation are eligible too, subject to current authorization.
Default processing path
The release embeds a fixed Go document parser compiled to WebAssembly/WASI. The control plane passes authorized attachment bytes to a fresh wazero instance and receives bounded JSON. The worker has:- No filesystem mounts, host paths, environment, credentials or socket API.
- No shell, child processes, arbitrary commands or model-supplied executable code.
- 256 MiB maximum linear memory, a 45-second execution deadline, and at most two active document workers per Mesh process.
- Immediate busy/retry error when both slots are occupied; no unbounded queue retaining uploads or consuming another document’s execution budget.
- 8 MiB input, 4 MiB JSON output, 500 PDF pages, 2,000 archive entries, 32 MiB declared/actual DOCX expansion, and 128 XML nesting levels.
- Rejection of encrypted/duplicate archive entries, DTD/entities and unsupported XML encodings. Relationships, macros and external resources are not resolved.
sh tools/document/build.sh using the pinned Go toolchain and module versions.
CI rejects source/asset drift; the image build independently rebuilds it. The
release carries the parsers: operators do not build or publish them.
Tool contract and coverage
list_documents() returns opaque attachment IDs, names, media types and sizes
from the current authorized conversation, bounded to the latest 100. It reports
whether older entries were omitted.
read_document(attachment_id, cursor?) returns the content SHA-256 revision,
parser version, located text, extraction limitations, delivered coverage and an
opaque continuation. With no cursor it starts at the beginning. Results are
bounded to 16,000 Unicode code points and 200 blocks; blocks can span responses.
Continuation is bound to attachment, revision and parser version, and every call
reauthorizes and refetches. A changed/deleted source fails rather than silently
splicing revisions. This release changes the parser version; old cursors must
restart from the beginning.
- PDF: physical page anchors. The bundled BSD-licensed Go PDF library reads embedded page text, not images, charts, form XObjects or annotations. Font decoding, layout and reading order can be imperfect; complex tables/columns are not faithfully reconstructed. PDF results always carry these limitations. Blank/scanned/unsupported pages are individually reported; entirely unreadable documents fail rather than claim a successful empty read.
- DOCX: paragraph and table-row anchors, not invented page numbers. Main-body paragraphs/tables only; headers, footers, notes, images, nested tables, text boxes, numbering and tracked-change semantics are not fully represented.
- TXT: UTF-8 (optional BOM), line anchors; binary/control-character content is rejected.
Permissions and retention
The existing conversation/evidence rules remain unchanged:- Queries scope attachment IDs to agent perspective and conversation and require a source audience compatible with the current audience. IDs are not authority.
- The owning Slack connector fetches using its credentials, after authorization, with size bounds and redirect/host restrictions. Credentials never enter WASI.
- Source message evidence is accumulated before reading; derived replies and memories inherit it and the existing destination-audience recheck.
- Document content is untrusted source material, never higher-priority instructions.
- Mesh does not persist originals, extracted documents or page images. Authorized excerpts can occur in existing tool/run records under their retention policy.
Optional remote processing
Existing deployments explicitly settingMESH_DOCUMENT_IMAGE retain their remote
processor override. This is advanced compatibility, not activation or normal
setup. An override requires an immutable parser image and an execution backend
that enforces no egress (currently Modal). Failure is explicit; an opted-in remote
policy never silently falls back to local processing. With the setting absent,
no execution-backend resolver or credential is consulted.
tools/document/Dockerfile and its Python/Poppler tests are retained for this
optional path. Its parser has different layout behavior from the bundled Go
parser; both expose extraction limitations. The default path needs neither
Python nor Poppler on the host.
Verification
go test ./internal/document/...runs real TXT/DOCX/PDF extraction through the embedded worker, pagination, partial/empty PDFs, malformed archives, output bounds, cancellation, WASI imports and hostile CPU/memory limit probes.- Turn tests exercise the native tools with the default processor and preserve authorization-before-fetch and source evidence checks. PostgreSQL tests cover cross-perspective, guessed-ID, wrong-conversation and wider-audience denial.
mesh document-checkruns fixed DOCX/PDF/TXT fixtures through the bundled reader without database, provider credentials or environment configuration. Image CI invokes it in the actual release image with network disabled, read-only filesystem, all capabilities dropped and no-new-privileges. This check gates publishing.- A live Slack upload still requires a normally provisioned Slack agent with
files:read; verify real DOCX/PDF uploads on the deployment before declaring the live incident resolved.
Conversation access failures
Anunknown audience means Mesh could not establish a safe disclosure scope,
not that the upload is absent or its format unsupported. Document tools return
an explicit access-verification error before querying attachment metadata or
fetching bytes. Reuploading or converting to PDF does not repair this condition.
For Slack, inspect the Slack audience verification failed warning in the
instance logs. Its sanitized reason identifies the failing API method and a
recognized Slack error code, HTTP status, timeout, or incomplete-evidence error.
Tokens, response bodies, channel identifiers, and membership lists are omitted.
A missing_scope error calls for checking the installed app’s conversation-read
permissions; not_in_channel calls for checking app membership. Neither is
assumed from a generic failure. Public externally shared channels remain
unverifiable by the current audience model and produce an unavailable warning.
These diagnostics do not relax authorization or repair previously stored
unknown source evidence. Verify a new conversation turn and upload after
correcting the underlying cause; historical evidence must not be relabeled from
current membership alone.