Skip to content

Documentation navigation

Artifacts

Artifact references, the clients that retrieve their bytes, and helpers for tools that produce files.

#Artifacts

Files that tools produce during a run, such as a DOCX report or a PNG chart, come back as immutable artifact references. This page covers the reference models, the two clients that retrieve the bytes, and the helpers for tools that produce files.

#How artifacts reach you

A completed run returns its generated files on response.artifacts, a tuple of ArtifactRef values. The same references ride on the assistant message, and one message carries at most 32.

Every reference names one revision of one file:

  • artifact_id identifies the revision. Editing a file creates a new revision with a new artifact_id.
  • logical_output_id stays the same across revisions of the same output.
  • revision starts at 1. Revision 2 and later set supersedes_artifact_id to the revision before it.
  • sha256 and size_bytes (ordinary references) let you check the bytes you download.

A filename does not identify a file. Use logical_output_id to follow an output across revisions.

There are two custody types, told apart by the custody field:

CustodyModelRetrieve with
'ordinary'OrdinaryArtifactRefclient.artifacts
'vault_private'PrivateArtifactRefclient.private_artifacts

ArtifactRef is the union of the two, discriminated on custody. Use pydantic.TypeAdapter(ArtifactRef) to validate a stored reference of either kind.

Python
from maivn import ArtifactRef, OrdinaryArtifactRef, PrivateArtifactRef

#OrdinaryArtifactRef

FieldTypeMeaning
artifact_id, logical_output_idstrRevision and output identity.
revisionintRevision number, at least 1.
supersedes_artifact_idstr | NoneThe previous revision, None for revision 1.
kindstr'document', 'spreadsheet', 'presentation', 'image', 'audio' or 'other'.
mime_typestrOne of the supported MIME types, such as application/pdf or image/png; application/octet-stream for 'other'.
custody'ordinary'Discriminator.
statestr'available', 'expired', 'deleting', 'deleted' or 'delete_incomplete'.
display_filenamestrFilename, up to 255 characters.
size_bytes, sha256int, strSize and lowercase hex SHA-256 of the exact bytes.
created_atstrRFC 3339 UTC timestamp.
effective_retentionmodelRetention policy frozen for this revision, with expires_at.
producer, validation_receiptmodelsValue-free producer lineage and validation evidence.
safe_previewmodel | NonePage, sheet, slide or pixel counts derived during validation. Never set for kind='other'.
retrieval_actionmodelThe relation used to authorize a download. It never embeds a URL.

#PrivateArtifactRef

A private reference describes a file held in the private vault. It carries no filename or content.

FieldTypeMeaning
artifact_id, logical_output_id, revision, supersedes_artifact_id, kind, mime_type, created_at, effective_retentionSame meaning as on OrdinaryArtifactRef.
custody'vault_private'Discriminator.
statestr'available_in_vault', 'expired', 'deleting', 'deletion_pending_backup_expiry', 'deleted' or 'delete_incomplete'.
display_labelstrA fixed label per kind, such as 'Private document'. Not the original filename.
creation_receipt_idstrReceipt for the creation.
deletion_receipt_idstr | NoneSet only in a deletion state.
producer, retrieval_actionmodelsValue-free lineage and download relation.

#Ordinary files: client.artifacts

client.artifacts lists, reads and downloads ordinary artifacts for the project your API key belongs to. Each method has an async twin with an a prefix.

Python
from pathlib import Path

from maivn import Client

client = Client(api_key='...')

page = client.artifacts.list(limit=20)
for item in page.artifacts:
    print(item.artifact_id, item.revision, item.display_filename, item.byte_size)

Path('downloads').mkdir(exist_ok=True)
for ref in response.artifacts:
    if ref.custody == 'ordinary':
        saved = client.artifacts.download_to(ref, Path('downloads') / ref.display_filename)
        print(saved.filename, saved.size_bytes, saved.sha256)
MethodReturnsNotes
list(*, invocation_id=None, state='available', revision=None, cursor=None, limit=50)ArtifactListPagestate is any lifecycle state or 'all'. The service accepts a limit from 1 to 200.
get(artifact_id, *, revision=None)ArtifactMetadataMetadata for one exact revision.
download(artifact_id_or_ref, *, revision=None)ArtifactDownloadPass an OrdinaryArtifactRef to check the bytes against its sha256, size_bytes and mime_type.
download_to(artifact_id_or_ref, path, *, revision=None)ArtifactDownloadVerifies the bytes, then replaces path atomically.
preview(artifact_id, *, revision)ArtifactPreviewTransient PDF, PNG or JPEG preview of one revision.
preview_to(artifact_id, path, *, revision)ArtifactPreviewVerifies the preview, then replaces path atomically.
download_source(ref, *, session_id)bytesPortable editable source for one revision. See Editing a file.

download, download_to and the preview methods raise ValueError when the response fails verification. When you pass a reference, a different revision argument also raises ValueError, because the reference already fixes the revision.

Pagination is cursor based. Pass page.next_cursor as cursor until it is None.

#Result models

ModelFields
ArtifactMetadataartifact_id, revision, state, invocation_id, logical_output_id, kind, mime_type, display_filename, byte_size, sha256, created_at, expires_at
ArtifactListPageartifacts (a tuple of ArtifactMetadata), next_cursor
ArtifactDownloadfilename, mime_type, size_bytes, sha256, content (bytes, hidden from repr)
ArtifactPreviewcontent (bytes), content_type, filename, sha256, source_revision_id

#Private files: client.private_artifacts

client.private_artifacts is a PrivateArtifactsClient. Private bytes move between your application and the same base_url as every other call. The SDK requires HTTPS for that address, with plain HTTP allowed only on the local loopback host.

For a reference you already hold, pass the reference and the session_id of the run that produced it. The client obtains the authorization and a transient retrieval proof for you.

Python
from pathlib import Path

Path('downloads').mkdir(exist_ok=True)
for index, ref in enumerate(response.artifacts):
    if ref.custody == 'vault_private':
        path = client.private_artifacts.download_to(
            ref,
            Path('downloads') / f'private-{index}.bin',
            session_id=response.session_id,
        )
MethodReturnsNotes
download(ref, *, session_id)bytesCollects the whole file in memory.
download_to(ref, path, *, session_id)PathStreams to disk and replaces path only after success. Partial bytes are removed on failure or cancellation.
download_source(ref, *, session_id)bytesEditable source for one revision.
export_redacted(ref, *, session_id, filename)RedactedExportReceiptCreates a separate ordinary artifact from a redacted copy. receipt.ordinary_artifact is its OrdinaryArtifactRef.
delete(ref, *, session_id, include_redacted_exports=False)PrivateWorkIntentStatusQueues deletion and returns the first committed status. include_redacted_exports=True also deletes retained redacted exports.
status(server_intent_id)PrivateWorkIntentStatusReads the status of an earlier request.
publish_file(output, *, source, session_id, call_id, output_index=0)PrivateArtifactRefPublishes an SDK-generated file and its portable source privately.

Each method has an async twin with an a prefix. RedactedExportReceipt and PrivateWorkIntentStatus live in maivn_contracts.artifacts.

export_redacted depends on the Vault holding a redacted source for the file, so it can fail for a file that has none. Deletion and export waits are bounded by the client's timeout_seconds; when the wait expires the call raises PrivateArtifactTimeoutError. Chunked reads accept a chunk_bytes from 1 to 65536.

#Explicit retrieval workflow

The lower-level methods split retrieval into steps. Most code does not need them.

  1. new_retrieval_proof() returns a TransientVaultClientProof.
  2. resolve(ref, *, session_id, proof, retrieval_part='private_original') returns the authorized work. Use retrieval_part='editable_source' for the source.
  3. retrieve_stream(work, *, proof, chunk_bytes=65536) yields the bytes in ranges. aretrieve_stream is the async generator.
  4. ingest_file(work, *, proof, output, source) uploads a private file and its source, and returns the new PrivateArtifactRef.

You can also pass the resolved work to download, download_to, export_redacted and delete instead of a reference. In that form you pass proof to the download methods and omit session_id.

TransientVaultClientProof is an opaque, process-local value. Its repr is redacted, it cannot be copied or pickled, and only its digest is sent in the work request. Create a fresh one for each retrieval.

#Editing a file

Editing a generated file means asking its toolset to render a new revision from the previous one. The server rejects a stale base revision and keeps earlier downloads. To resume an edit:

  1. Download the portable source of the exact revision with client.artifacts.download_source(ref, session_id=...), or client.private_artifacts.download_source(ref, session_id=...) for a private file.
  2. Pass the bytes and the reference to the matching toolset's resume_artifact or resume_private_artifact.

The toolset's next render carries that revision as its expected base.

Use the session_id from the authenticated SDK run for that thread, not a Studio session identifier. The source restores mAIvn composition sources, including embedded images. It is not a general importer for Office files.

#Producing files from a tool: maivn.files

A custom toolset can return a file that the SDK publishes for you. Annotate the tool's return type as GeneratedFile or GeneratedFiles and return the result of generated_file(path).

Python
from pathlib import Path

from maivn import toolify, toolset
from maivn.files import GeneratedFile, generated_file


@toolset
class ReportTools:
    def __init__(self, output_dir: Path) -> None:
        self._output_dir = output_dir

    def authorized_generated_file_roots(self) -> tuple[Path, ...]:
        return (self._output_dir,)

    @toolify(description='Write a one-line CSV report')
    def write_report(self, title: str) -> GeneratedFile:
        path = self._output_dir / 'report.csv'
        path.write_text(f'title\n{title}\n', encoding='utf-8')
        return generated_file(path)

The toolset must define authorized_generated_file_roots() and return the directories the SDK may read from. A file outside those roots is refused.

NamePurpose
generated_file(path, *, custody='ordinary', mime_type=None, source=None, source_format=None, expected_base=None, private_expected_base=None)Freezes a copy of one local file and returns a GeneratedFile that declares its exact bytes. The file must be non-empty and at most 50 MiB (52,428,800 bytes).
GeneratedFileOne declared file: filename, kind, MIME type, size, SHA-256, custody and an optional expected base revision.
GeneratedFilesAn ordered batch of 1 to 16 GeneratedFile values for tools that return several outputs.
editable_source_archive(output, source=None, *, source_format=None)Wraps optional source bytes in the maivn-source-archive-v1 envelope.

Notes on generated_file:

  • The snapshot is written under a .maivn-generated folder next to your file. Your original file is never changed or removed.
  • .docx, .pdf, .xlsx, .pptx, .png and .jpg or .jpeg keep their typed checks. Every other extension is stored as application/octet-stream with no inline preview.
  • custody='vault_private' routes the file to the private vault. Pass expected_base for an ordinary revision or private_expected_base for a private one when you are producing the next revision. A single file cannot set both.
  • Pass source and a source_format such as 'acme-sheet/1' together to attach editable source that only your own versioned adapter reads. source_format without source raises ValueError.

Private output needs a compatible private-file service and a configured retention policy. External webhook tool callbacks refuse private artifact references.

#Image inputs for toolsets: maivn.artifact_images

Toolsets that compose images, such as a document or presentation builder, receive the bytes of images the run already selected. They read them through maivn.artifact_images.

Python
from maivn.artifact_images import (
    ArtifactImageUnavailableError,
    ResolvedArtifactImage,
    resolve_artifact_image,
)

await resolve_artifact_image(artifact_id) returns a ResolvedArtifactImage with content (bytes), artifact_ref, mime_type and private (whether the source was private). It only resolves images selected by the current invocation's trusted references. Outside a run, or for an image that is not selected, it raises ArtifactImageUnavailableError, a ValueError with a fixed message.

Keep these bytes local. Do not return them as a tool result, because the model would see them. Return an opaque handle for the composed file instead.

#Errors

Private retrieval and lifecycle calls raise PrivateArtifactError or one of its subclasses. The messages are fixed and carry no file content. Branch on the class or on exc.code.

ExceptioncodeMeaning
PrivateArtifactErrorprivate_artifact_request_refusedBase class. Any rejected or malformed request, including a mismatched proof or session.
PrivateArtifactAuthorizationDeniedErrorprivate_artifact_authorization_deniedThe operation was admitted and then denied.
PrivateArtifactGrantExpiredErrorprivate_artifact_grant_expiredThe grant expired before the operation finished.
PrivateArtifactEffectFailedErrorprivate_artifact_effect_failedThe operation reached a terminal failure in the vault.
PrivateArtifactTimeoutErrorprivate_artifact_timeoutThe bounded wait for the final status expired.

Ordinary retrieval raises MaivnHTTPError for non-2xx responses and ValueError when verification fails. See Errors and exceptions.

#See also