Skip to content
Before you attach files
Public Preview

Attach files to a Task: prepare, upload, and submit

This how-to describes preparing, uploading, and submitting file attachments to a Task using the Python SDK, which creates a Library under the agent path.

This page covers prerequisites, the prepare-upload-submit workflow, verification, image handling, continuation attachments, and security considerations for attaching files.

  • Prerequisites: tenant, token, agent identity, and file limits.
  • Prepare, upload, and submit workflow using task_id.
  • Verification of attached documents and partial upload recovery.
  • Image support in documents and continuation task attachments.
  • Security considerations for confidential data and storage access.

Summaries were generated by AI.

Use separate SDK calls to prepare files, upload them, wait until they are readable, and submit the Task. The SDK creates or resolves a Library at agent/<agent_id>/task/<task_id> and returns its Library and document IDs.

Before you attach files

Complete the Python SDK quickstart so the package, Tenant, token, and eligible model pool are ready.

Confirm these inputs before the upload:

  • Set DUALEAI_TENANT_ID. Attachment uploads go through the tenant’s Library.
  • Set DUALEAI_TOKEN to an API token for the provisioned Agent Identity.
  • Provide the provisioned Agent Identity’s public identifier through upload_attachments(agent_id=...) or DUALEAI_AGENT_ID. The per-call value overrides the environment setting. The identifier selects the Library path; the token still identifies the caller.
  • For the default path, use the token’s own Agent Identity ID as agent_id. The supplied Agent default lets every Agent Identity start uploads and create, write, and read Libraries under its own agent/<agent_id>/ path, so that identity needs no further access there. Another agent_id selects another Agent Identity’s folder, which needs library:write and library:read on that path. Access and isolation explains how Tenant access policies and Library Grants supply these actions.
  • Use non-empty files of supported types, each no larger than 500 MiB. This example uses a synthetic contract.pdf.
  • Arrange Dashboard deletion with a Human Identity who has library:delete and valid aal3 authentication when the files are no longer needed.

Library limits lists the supported file types; the Dashboard upload accepts the same list. A file of an unsupported type uploads, then fails processing with FORMAT_UNSUPPORTED.

This workflow stores documents and submits a real Task. It can incur Platform and model-provider usage under your agreement; see Pricing and access. Use non-sensitive test data.

Prepare, upload, submit

prepare_attachments reads file sizes without loading file contents and raises FileNotFoundError for a missing path. Finish writing each file before preparation, and keep it unchanged until upload completes. Create one task_id for the upload and pass the same task_id to ask(). This links the logs and Library path to the Task:

import asyncio
from pathlib import Path
from uuid import uuid4

from dualeai import (
    LibraryDocumentGetRequest,
    LibraryResponseDocumentStatus,
    ask,
    create_sdk,
)


async def main() -> None:
    async with create_sdk() as sdk:
        attachments = sdk.prepare_attachments(
            [(Path("contract.pdf"), "Synthetic example contract")],
        )
        task_id = str(uuid4())
        print(f"task_id={task_id}")

        receipts = await sdk.upload_attachments(
            task_id,
            attachments,
        )
        receipt = receipts[attachments[0].key]
        print(f"library_id={receipt.library_id}")
        print(f"document_id={receipt.document_id}")
        document = await sdk.libraries.wait_for_document(
            LibraryDocumentGetRequest(
                library_id=receipt.library_id,
                document_id=receipt.document_id,
            ),
        )
        if document.status is LibraryResponseDocumentStatus.failed:
            print(document.failure)
            return

        # Submit only after the document is readable.
        response = await ask(
            action="Summarize the attached contract.",
            attachments=attachments,
            task_id=task_id,
            sdk=sdk,
        )
        print(await response.model())


asyncio.run(main())

Save the example as attach_document.py, keep contract.pdf beside it, then run:

python attach_document.py

Verify the attached document

A successful run prints the Task, Library, and document identifiers before it waits for the document or the Task Result. It then prints the model’s summary of the uploaded document.

Keep the IDs for later checks and cleanup. Have the cleanup owner delete the Library in the Dashboard when it is no longer needed. API-token sessions are AAL2 and cannot delete a whole Library. Security and data handling explains this irreversible operation.

upload_attachments returns receipts keyed by PreparedAttachment.key. Use that key to match each receipt to its prepared file and Task attachment metadata. The description explains the file’s purpose. Pass the prepared attachments to ask(); you do not pass a Library ID.

For several files, add (path, description) tuples to prepare_attachments. A receipt confirms only that a document is queued. Poll every document to ready and handle any failed status before submitting a Task that needs all files. Use Manage libraries to choose the Library path or manage it independently.

An upload batch can fail after some files have succeeded. Save LibraryUploadError.completed_receipts and check which documents were already created before repeating an upload. Recover a partial upload gives the recovery steps.

Images in a document

Charts, diagrams, and scans can reach the model as images, including scans with no extractable text. The Tenant’s pool needs an image-capable Model Route. Images states when an image reaches the model and when a note replaces it.

Verify image support by asking for a value that appears only inside a figure.

Attach documents to a continuation

A continuation Task keeps the parent’s documents and can add new ones. Create the continuation’s identifier, upload the new files under it with sdk.upload_attachments(task_id, ...), then pass the same identifier and the prepared attachments to next(task_id=..., attachments=...).

Before you attach confidential data

  • The Task path selects the Library; the token’s caller decides access. Duale AI checks that caller’s policies and Grants against the path agent/<agent_id>/task/<task_id>. Choosing a task_id grants no Library access.
  • Later Tasks of the same Agent Identity can reach these files. Its default access covers every Library under its own agent/<agent_id>/ path, so Library search can reach them in any of its Tasks. Access and isolation explains that boundary.
  • Task Libraries do not expire on their own. Their documents count toward recorded storage volume until deletion. Tasks with attachments can each leave a Library to clean up. Pricing and access explains whether your agreement bills that volume separately.
  • File bytes go from your process to a separate storage host. The SDK sends them over HTTPS, so allow that host in any egress policy. Each upload URL is signed and time-limited: treat it as a credential for its lifetime, and keep it out of application logs. Data protection states that lifetime and explains the temporary upload area.
  • Library API calls are encrypted end to end. The SDK encrypts them between your process and Duale AI, in addition to HTTPS. File bytes sent to the storage host use HTTPS only. End-to-end encryption maps that boundary.

Test access to the storage host and Library before uploading.