← All code samples

Chat session attachments

Send a file alongside a chat message, then manage the conversation and the attachment afterward.

  1. startAgentChatWithFiles posts multipart/form-data instead of JSON
  2. Wait for the turn with waitForSubscribeComplete / SubscribeExecution
  3. updateSession to rename and star the conversation
  4. downloadSessionAttachment + deleteSessionAttachments once you have an attachment_id

Where the attachment id comes from

There is no list-attachments endpoint. Aiden gives the uploaded file a stable attachment://<session_id>/<attachment_id> handle, but that handle only shows up inside the turn’s own content: in a tool call’s arguments for reference-mode agents, in the model’s reply when you ask it to repeat the handle, or in the execution debug ZIP (downloadExecutionDebugZip). Capture the UUID from one of those, then call downloadSessionAttachment / deleteSessionAttachments directly — you do not need to re-discover it on every run.

When you need this

  • Let a user attach a log file or config snippet to a question. Inline agents (default) get the bytes directly, capped at 3 MiB per file. Reference agents get the handle instead and fetch the file on demand, up to 10 MiB.
  • Label a run after the fact. updateSession only touches the fields you pass, so renaming does not disturb starred or archived.
  • Clean up test fixtures. deleteSessionAttachments removes stored blobs and best-effort RAG vector rows for a session in one call.

How to run

export STACKGEN_URL=https://app.stackgen.com
export STACKGEN_TOKEN=stackgen_…
export STACKGEN_WORKSPACE_ID=<workspace-uuid>

pip install stackgen-sdk==0.4.0
python examples/automation/chat-session-attachments.py --agent my-ops-agent

Use an agent that already exists in the workspace. Pass --attachment-id once you have one (see above) to also exercise download + delete.

Minimal

from stackgen import StackgenClient, StackgenConfig

client = StackgenClient(
    StackgenConfig(
        base_url="https://app.stackgen.com",
        api_token="stackgen_…",
        workspace_id="<workspace-uuid>",
    )
)

run = client.aiden.start_agent_chat_with_files(
    "my-ops-agent",
    message="Summarize the attached file in one sentence.",
    files=[{"filename": "notes.txt", "content": b"Deploys succeed when health checks pass.\n"}],
)
client.aiden.wait_for_subscribe_complete(run["trace_id"])

client.aiden.update_session(run["session_id"], title="Attachment summary", starred=True)

# attachment_id comes from the turn's own content (see "Where the attachment id
# comes from" above) -- it is not returned by start_agent_chat_with_files.
attachment_id = "8f14e45f-…"
client.aiden.download_session_attachment(run["session_id"], attachment_id, "./downloaded.txt")
client.aiden.delete_session_attachments(run["session_id"])

Notes

  • message may be empty when at least one file is attached.
  • Aiden accepts multiple file parts per request (cap 10; 3 MiB each for inline agents, 10 MiB for reference agents; 40 MiB total body).
  • updateSession / update_session / UpdateSessionMetadata only writes the fields you set — pass title: "" to clear a previously set title.
  • deleteSessionAttachments 404s when no attachment session exists for that session_id.

Complete scripts

#!/usr/bin/env python3
"""Send a chat turn with a file attachment, then rename/star the session and
tidy up the attachment.

``start_agent_chat_with_files`` posts ``multipart/form-data``; Aiden stores
each file and gives the agent an ``attachment://<session_id>/<attachment_id>``
handle instead of the raw bytes (for reference-mode agents) or inlines it
(inline agents, capped at 3 MiB/file). There is no list-attachments endpoint,
so the handle only shows up in the turn's own content -- ask the agent to
repeat it back, or scan the execution debug ZIP. Pass --attachment-id once
you have one to also exercise download + delete.

Usage:
  export STACKGEN_URL=https://app.stackgen.com
  export STACKGEN_TOKEN=stackgen_…
  export STACKGEN_WORKSPACE_ID=<workspace-uuid>
  python examples/automation/chat-session-attachments.py --agent my-ops-agent
  python examples/automation/chat-session-attachments.py \
    --agent my-ops-agent --attachment-id 8f14e45f-…
"""

from __future__ import annotations

import argparse
import os
import tempfile
from pathlib import Path

from stackgen import StackgenClient, StackgenConfig


def env(name: str, *fallbacks: str) -> str:
    for key in (name, *fallbacks):
        value = (os.environ.get(key) or "").strip()
        if value:
            return value
    raise SystemExit(f"missing env {name}")


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--agent", required=True, help="Agent name to chat with")
    parser.add_argument(
        "--attachment-id",
        default="",
        help="UUID from a previously seen attachment:// handle; also exercises download + delete",
    )
    args = parser.parse_args()

    client = StackgenClient(
        StackgenConfig(
            base_url=env("STACKGEN_URL"),
            api_token=env("STACKGEN_TOKEN"),
            workspace_id=env("STACKGEN_WORKSPACE_ID", "STACKGEN_PROJECT"),
        )
    )

    run = client.aiden.start_agent_chat_with_files(
        args.agent,
        message="Summarize the attached file in one sentence.",
        files=[
            {
                "filename": "sdk-sample-notes.txt",
                "content": b"Deploys succeed when the health check passes within 60s.\n",
                "content_type": "text/plain",
            }
        ],
    )
    session_id = str(run.get("session_id") or run.get("sessionId") or "")
    trace_id = str(run.get("trace_id") or run.get("traceId") or "")
    print(f"started chat session_id={session_id} trace_id={trace_id}")

    client.aiden.wait_for_subscribe_complete(trace_id, timeout_ms=5 * 60 * 1000)
    print("turn complete")

    updated = client.aiden.update_session(
        session_id, title="SDK sample: attachment summary", starred=True
    )
    print(f"renamed session title={updated.get('title')} starred={updated.get('starred')}")

    if not args.attachment_id:
        print(
            "no --attachment-id passed; skipping download_session_attachment. "
            "Capture an attachment_id from the agent's reply or the execution debug ZIP, "
            "then rerun with --attachment-id."
        )
        return 0

    with tempfile.TemporaryDirectory(prefix="sdk-attachment-") as tmp:
        dest = client.aiden.download_session_attachment(
            session_id, args.attachment_id, str(Path(tmp) / "downloaded.txt")
        )
        print(f"downloaded attachment to {dest}")

    client.aiden.delete_session_attachments(session_id)
    print(f"deleted all attachments for session_id={session_id}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

See also