API reference¶
The reference is generated from artifactr.files' docstrings and type annotations. It documents each package's public API: the names in its __all__.
artifactr.files.manifest¶
Native files as artifacts: the manifest types, and the revision that tests its base.
An artifact of a native type is a small Pydantic manifest. It names the file's bytes, by their
SHA-256, in a Blobs store, and a revision is ordinary JSON Patch
that first tests the digest it was made from (revise).
REVISED
module-attribute
¶
REVISED: Final = (
"content",
"provenance",
"fingerprint",
"text",
"review",
"outline",
"pages",
)
What a revision replaces: everything in the manifest but the file's name.
DOCX
module-attribute
¶
DOCX: Final = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
The media type of a Word document, .docx.
DXF
module-attribute
¶
DXF: Final = 'image/vnd.dxf'
The media type of a drawing in AutoCAD's exchange format, .dxf.
IDML
module-attribute
¶
IDML: Final = 'application/vnd.adobe.indesign-idml-package'
The media type of an InDesign markup package, .idml.
OUTLINE_LIMIT
module-attribute
¶
OUTLINE_LIMIT: Final = 4000
The most characters an outline holds: what the agent sees of a file by default.
PPTX
module-attribute
¶
PPTX: Final = "application/vnd.openxmlformats-officedocument.presentationml.presentation"
The media type of a PowerPoint presentation, .pptx.
PSD
module-attribute
¶
PSD: Final = 'image/vnd.adobe.photoshop'
The media type of a Photoshop document, .psd.
XLSX
module-attribute
¶
XLSX: Final = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
The media type of an Excel workbook, .xlsx.
Sha256
module-attribute
¶
A SHA-256 digest, as 64 lowercase hexadecimal digits.
Source
module-attribute
¶
Source = Literal['upload', 'check_in', 'commands']
How a revision's content was made: uploaded, checked in by a person, or by an app's commands.
Blob
pydantic-model
¶
NativeFile
pydantic-model
¶
Bases: Artifact
A file in a native format: its manifest. Subclass it once per format.
Agents' edits are proposals by default (write_policy), and write policies bind agents
only: a person's check-in commits directly.
Config:
extra:forbidvalidate_assignment:True
Fields:
-
name(str) -
content(Blob) -
fingerprint(Sha256) -
text(Blob | None) -
review(Blob | None) -
outline(str) -
pages(int | None) -
provenance(Provenance)
Validators:
-
_of_its_format
fingerprint
pydantic-field
¶
fingerprint: Sha256
The digest of the content, normalized: what each save stamps, such as the time, left out, so that a save that changes nothing else makes no revision.
Provenance
pydantic-model
¶
Bases: BaseModel
How a revision's content was made. Who made it is the revision's actor.
It is a claim: any surface that commits edit_artifact can write anything here, so nothing
is authorized by it.
Config:
frozen:Trueextra:forbid
Fields:
WordDocument
pydantic-model
¶
Bases: NativeFile
A Word document, .docx: registered as word_document.
Fields:
-
name(str) -
content(Blob) -
fingerprint(Sha256) -
text(Blob | None) -
review(Blob | None) -
outline(str) -
pages(int | None) -
provenance(Provenance)
Validators:
-
_of_its_format
revise
¶
revise(
artifact_id: ArtifactId,
after: NativeFile,
*,
base_version: int,
tested: str,
summary: str,
thread_id: ThreadId | None = None,
) -> EditArtifact
Build the edit that gives an artifact after's content, if its content is tested.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
artifact_id
|
ArtifactId
|
The native file to revise. |
required |
after
|
NativeFile
|
The manifest it is to have; its name is not changed. |
required |
base_version
|
int
|
The version the edit is based on. |
required |
tested
|
str
|
The content digest the edit was made from, which the patch tests first. |
required |
summary
|
str
|
The change note, from the batch or the check-in. |
required |
thread_id
|
ThreadId | None
|
The thread the edit is made in, if any. |
None
|
artifactr.files.blobs¶
The blob store: the bytes of native files, by tenant, workspace, artifact and digest.
Blobs is the port, with
InMemoryBlobs here and
SqlBlobs in the sql extra. Bytes are put first, then the
revision that names them is committed; what no revision or pending proposal names is collected
later, so a put that was never committed is only garbage.
DEFAULT_LIMIT
module-attribute
¶
DEFAULT_LIMIT: Final = 25 * MIB
How large a blob may be unless the store is told otherwise.
Clock
module-attribute
¶
Returns the current time; injectable, so that tests control when a blob was put.
InMemoryBlobs
¶
InMemoryBlobs(
*, clock: Clock = utc_now, limit: int = DEFAULT_LIMIT
)
The whole Blobs port in memory: nothing outlives it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
clock
|
Clock
|
Returns the current time, which a put is stamped with. |
utc_now
|
limit
|
int
|
The largest blob, in bytes: 25 MiB unless given, and at most 100 MiB. |
DEFAULT_LIMIT
|
put
async
¶
put(
scope: Scope,
artifact_id: ArtifactId,
data: AsyncIterable[bytes],
media_type: str,
) -> Blob
Store bytes for an artifact, and name them, as Blobs.put does.
get
async
¶
get(
scope: Scope, artifact_id: ArtifactId, sha256: str
) -> bytes | None
Return an artifact's bytes with this digest, or None if it has none.
Blobs
¶
Bases: Protocol
Where the bytes of native files are kept, by artifact and digest.
put
async
¶
put(
scope: Scope,
artifact_id: ArtifactId,
data: AsyncIterable[bytes],
media_type: str,
) -> Blob
Store bytes for an artifact, and name them.
The digest and size are computed as the bytes arrive, never taken from a client. Putting
bytes the artifact already has stores nothing new, and marks them as put now, so that
collect keeps them for the commit that follows.
Raises:
| Type | Description |
|---|---|
TooLarge
|
If the bytes are larger than the store's limit. |
get
async
¶
get(
scope: Scope, artifact_id: ArtifactId, sha256: str
) -> bytes | None
Return an artifact's bytes with this digest, or None if it has none.
collect
async
¶
Delete the workspace's blobs last put before before that aren't referenced.
A blob put again meanwhile is kept, whatever referenced says.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scope
|
Scope
|
The workspace. |
required |
referenced
|
Set[tuple[ArtifactId, str]]
|
Every |
required |
before
|
datetime
|
Only blobs last put before this, such as a day ago. |
required |
Returns:
| Type | Description |
|---|---|
int
|
How many were deleted. |
TooLarge
¶
TooLarge(limit: int)
Bases: Exception
The bytes are larger than the store's limit; nothing was stored.
Attributes:
| Name | Type | Description |
|---|---|---|
limit |
The store's limit, in bytes. |
once
async
¶
once(data: bytes) -> AsyncIterable[bytes]
Bytes already in memory, as the stream that Blobs.put takes.
artifactr.files.checkouts¶
Check-outs: the record behind a person's lease on a native file.
Checkouts is the port, with
InMemoryCheckouts here and
SqlCheckouts in the sql extra.
InMemoryCheckouts
¶
Checkout
pydantic-model
¶
Bases: BaseModel
A person's check-out of a native file.
Config:
frozen:True
Fields:
-
id(str) -
artifact_id(ArtifactId) -
base_version(int) -
base_sha256(Sha256) -
actor(Actor) -
expires_at(datetime) -
checked_in(bool)
base_version
pydantic-field
¶
base_version: int
The version checked out, which a check-in is based on.
base_sha256
pydantic-field
¶
base_sha256: Sha256
The content digest checked out, which a check-in's patch tests.
Checkouts
¶
artifactr.files.app¶
The app port: a native application, run headless, that edits a file by its own commands.
App is the port, and FakeApp the
fake that every pull request's tests run with. Each real app's adapter is a layer of its own,
starting with WordCraft's, artifactr.files.wordcraft.
Failure
module-attribute
¶
Failure = Literal[
"timeout", "crashed", "too_large", "refused"
]
How a call can fail: past its deadline, the app crashed, a limit was breached, or the app refused the file or a command.
Talk
module-attribute
¶
Talk = Literal[
"save", "bridged", "commands", "polled", "pushed"
]
How much the app talks to the host while a person edits in it (lattice RFC-0003): save, only
the bytes when the person saves (level 1); bridged, through the editor's shim, the host opens
the file in the editor and hears each save and whether there are unsaved changes, but sends no
commands (level 1+, native files ADR-0010); commands, the host's commands, with replies;
polled, changes the host asks for; pushed, changes as they happen.
FakeApp
¶
A fake App, for tests: it reads and writes its own JSON files.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fails
|
Failure | None
|
How every call fails, to test what happens when the app does; None for never. |
None
|
integration
|
str | None
|
The key that switches it, if not its class's, |
None
|
file
staticmethod
¶
Make a file, as an upload would bring it.
apply
async
¶
apply(
source: bytes | None,
commands: Sequence[AppCommand],
*,
deadline: float,
) -> Applied
Apply a batch of commands; refuse the batch if any command doesn't apply.
inspect
async
¶
inspect(source: bytes, *, deadline: float) -> Inspection
Read a file's fingerprint, outline, text, review and pages.
render
async
¶
Render a page as a one-pixel PNG whose colour comes from the page's paragraphs.
commands
async
¶
commands(query: str) -> Sequence[CommandSpec]
Find the commands whose name or description mentions the query.
App
¶
Bases: Protocol
A native application, run headless, that edits one format by its own commands.
Every call is given a deadline, in seconds, and fails with
AppFailed when it can't finish.
talk
property
¶
talk: Talk
How much the app talks to the host while a person edits in it.
At save, a person checks the file out, edits it in the app and checks it in.
integration
property
¶
integration: str
The key that switches the app on or off in a workspace, such as files:wordcraft.
apply
async
¶
apply(
source: bytes | None,
commands: Sequence[AppCommand],
*,
deadline: float,
) -> Applied
Apply a batch of commands to a file, or to a new one if source is None.
inspect
async
¶
inspect(source: bytes, *, deadline: float) -> Inspection
Read a file's fingerprint, outline, text, review and pages.
render
async
¶
Render one page of a file, from 1, as a PNG.
commands
async
¶
commands(query: str) -> Sequence[CommandSpec]
Find the commands that match a query, ten at most.
AppCommand
pydantic-model
¶
AppFailed
¶
Applied
dataclass
¶
Applied(content: bytes, inspection: Inspection)
A batch of commands applied: the new bytes, and what the app read from them.
CommandSpec
pydantic-model
¶
Inspection
pydantic-model
¶
Bases: BaseModel
What an app reads from a file.
Config:
frozen:True
Fields:
-
fingerprint(Sha256) -
outline(str) -
text(str) -
review(dict[str, JsonValue]) -
pages(int | None) -
media_type(str | None)
artifactr.files.craft¶
The crafting apps: each declared once, and run headless through one of two dialects.
CRAFTS declares every app, a
Craft each, as its pinned release offers it. An app's adapter
subclasses one of the two dialects its CLI's MCP server speaks,
ToolsDialect (WordCraft's) or
CommandsDialect (PdfCraft's), over
Spawn, which starts the CLI for each call, confined to a
directory of the call's own (native files ADR-0009).
PREVIEW
module-attribute
¶
PREVIEW: Final = 1024
How many pixels a page's longer side is rendered at, at scale 1.
CRAFTS
module-attribute
¶
CRAFTS: Final = _registry(
Craft(
name="wordcraft",
title="WordCraft",
dialect="tools",
roots="split",
formats=frozenset({DOCX}),
render=True,
reach="file",
),
Craft(
name="gridcraft",
title="GridCraft",
dialect="tools",
roots="none",
formats=frozenset({XLSX}),
render=False,
reach="file",
bridge="parent",
),
Craft(
name="deckcraft",
title="DeckCraft",
dialect="tools",
roots="none",
formats=frozenset({PPTX}),
render=True,
reach="file",
),
Craft(
name="pdfcraft",
title="PdfCraft",
dialect="commands",
roots="single",
formats=frozenset({PDF}),
render=True,
reach="file",
bridge="parent",
notices=(
Notice(
licence="CC-BY-SA-4.0",
material="The text detection and recognition models its OCR runs (ocrs-models)",
attribution="Robert Knight, ocrs-models (https://github.com/robertknight/ocrs-models), trained on HierText",
files=(
"share/pdfcraft/models/ATTRIBUTION.txt",
"share/pdfcraft/models/text-detection.rten.LICENCE.txt",
"share/pdfcraft/models/text-recognition.rten.LICENCE.txt",
),
),
),
),
Craft(
name="cadcraft",
title="CADCraft",
dialect="tools",
roots="none",
formats=frozenset({DXF, DWG}),
render=True,
reach="file",
bridge="parent",
),
Craft(
name="photocraft",
title="PhotoCraft",
dialect="commands",
roots="split",
formats=frozenset({PSD}),
render=True,
reach="file",
),
Craft(
name="vectorcraft",
title="VectorCraft",
dialect="commands",
roots="split",
formats=frozenset({SVG}),
render=True,
reach="file",
),
Craft(
name="designcraft",
title="DesignCraft",
dialect="tools",
roots="split",
formats=frozenset({IDML}),
render=True,
reach="file",
),
Craft(
name="figcraft",
title="FigCraft",
dialect="tools",
roots="none",
formats=frozenset(),
render=False,
reach="file",
),
Craft(
name="filmcraft",
title="FilmCraft",
dialect="commands",
roots="none",
formats=frozenset(),
render=True,
reach="export",
bridge="global",
),
Craft(
name="effectcraft",
title="EffectCraft",
dialect="commands",
roots="split",
formats=frozenset(),
render=True,
reach="export",
bridge="global",
),
Craft(
name="soundcraft",
title="SoundCraft",
dialect="tools",
roots="split",
formats=frozenset(),
render=False,
reach="export",
),
Craft(
name="lightcraft",
title="LightCraft",
dialect="commands",
roots="none",
formats=frozenset(),
render=True,
reach="export",
bridge="global",
),
)
The crafting apps, by name, read-only: what each pinned release offers, checked on 2026-10-18 against its CLI's own MCP tools and options.
Bridge
module-attribute
¶
Bridge = Literal['parent', 'global']
The host surface of the browser build, as released: parent, a postMessage bridge to a
same-origin parent page (?host=parent) that opens a file and hears its saves and whether it
has unsaved changes; global, a JavaScript API on the page's window.
Dialect
module-attribute
¶
Dialect = Literal['tools', 'commands']
How the CLI's MCP server is driven: tools, a tool for each step (open_document, batch,
save_document, list_commands), as WordCraft's; commands, every step a command run through
command_run and command_batch, rendered by render_preview, as PdfCraft's and FilmCraft's
(whose servers read a file through doc_inspect).
Reach
module-attribute
¶
Reach = Literal['file', 'export']
What lattice keeps of the app's work: file, the file itself, as a native file that people
and agents edit; export, only what the app renders or exports from a project of its own, as a
single file, since its projects point at media outside them.
Roots
module-attribute
¶
Roots = Literal['split', 'single', 'none']
How the CLI confines the files it reads and writes: split, to a read root and a write root
(--automation-read-root, --automation-write-root); single, to one root for both (--root);
none, to nothing, so it reads and writes wherever its user can.
CommandsDialect
¶
CommandsDialect(
cli: str | Path,
*,
version: str,
arguments: Sequence[str] = (),
trusted: bool = False,
limit: int = 256 << 20,
lookup: float = 10.0,
)
Bases: Headless
An app whose CLI's MCP server runs every step as a command, spawned for every call.
A subclass names its app's craft, its files' extension, and how it reads a file,
inspection; and, where they differ from PdfCraft's, its commands and the parameter that
names the open document.
new_command
class-attribute
¶
new_command: str | None = None
The command that makes a blank file, or None if the server has none headless.
handle
class-attribute
¶
handle: str | None = 'doc'
The parameter by which each command names the open document, as the open command's result names it; None for a server whose commands act on the document open last.
open
async
¶
Open the source, or make a blank file, and return what the server calls it.
Raises:
| Type | Description |
|---|---|
AppFailed
|
|
batch
async
¶
batch(
server: Server,
document: JsonValue,
commands: Sequence[AppCommand],
) -> None
Run the commands through command_batch, which stops at the first that fails.
A step its reply says failed refuses the batch, whether the reply is an error or not.
save
async
¶
Save the file into the write root by the save command, and return its bytes.
page
async
¶
Render one page through render_preview, its longer side PREVIEW times scale.
find
async
¶
find(server: Server, query: str) -> Sequence[CommandSpec]
The commands command_list finds for the query, ten at most.
Headless
¶
Headless(
cli: str | Path,
*,
version: str,
arguments: Sequence[str] = (),
trusted: bool = False,
limit: int = 256 << 20,
lookup: float = 10.0,
)
Bases: ABC
A crafting app's CLI, spawned for every call: an App.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cli
|
str | Path
|
The app's CLI, |
required |
version
|
str
|
The version it must be, which provenance records: a CLI of another version is refused. |
required |
arguments
|
Sequence[str]
|
What the CLI is given after |
()
|
trusted
|
bool
|
Whether its files are trusted, or this process confined: required of an app whose CLI confines nothing. |
False
|
limit
|
int
|
The most bytes a file may hold, saved or inflated: 256 MiB by default. |
256 << 20
|
lookup
|
float
|
How long, in seconds, finding commands may take: 10 by default. |
10.0
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the app's CLI confines nothing and |
extension
class-attribute
¶
extension: str
What the source and the saved file are named with, such as .docx.
build
class-attribute
instance-attribute
¶
build: str | None = None
It runs on this machine, in no image.
talk
property
¶
talk: Talk
bridged if its build has a ?host=parent bridge, which the shim reaches; else save.
A JavaScript API could carry commands, but no app's has been run through the shim, so none is claimed.
apply
async
¶
apply(
source: bytes | None,
commands: Sequence[AppCommand],
*,
deadline: float,
) -> Applied
Run a batch of commands on a file, or a blank one, and save it.
The batch stops at the first command that fails, which refuses it whole.
inspect
async
¶
inspect(source: bytes, *, deadline: float) -> Inspection
Read a file's fingerprint, outline, text, review and pages.
render
async
¶
Render one page of a file, from 1, as a PNG, at scale times the app's own size.
Raises:
| Type | Description |
|---|---|
AppFailed
|
|
commands
async
¶
commands(query: str) -> Sequence[CommandSpec]
Find the commands whose id, label or place in the app mentions the query.
open
abstractmethod
async
¶
Open the source, or a blank file if it is None, and return what the server calls it.
batch
abstractmethod
async
¶
batch(
server: Server,
document: JsonValue,
commands: Sequence[AppCommand],
) -> None
Run the commands in order on the open file, refused whole if one fails.
save
abstractmethod
async
¶
Save the open file into the write root, and return its bytes.
page
abstractmethod
async
¶
Render one page of the open file, from 1, as a PNG.
find
abstractmethod
async
¶
find(server: Server, query: str) -> Sequence[CommandSpec]
The commands that match the query, ten at most.
inspection
abstractmethod
async
¶
inspection(
server: Server, document: JsonValue, content: bytes
) -> Inspection
Read the open file, whose bytes are content, into an inspection: each app's own.
It names the format it read the file as, media_type, which a check-in is held to.
Craft
dataclass
¶
Craft(
name: str,
title: str,
dialect: Dialect,
roots: Roots,
formats: frozenset[str],
render: bool,
reach: Reach,
bridge: Bridge | None = None,
notices: tuple[Notice, ...] = (),
)
One crafting app: what its pinned release offers, and how lattice may use it.
name
instance-attribute
¶
name: str
Its name in lowercase, such as wordcraft: of its key, its CLI and its repository.
formats
instance-attribute
¶
The media types of the native files it keeps; none for an app whose formats have no
registered type yet, or whose reach is export.
integration
property
¶
integration: Integration
Its integration in lattice-core's registry: its default, and its admin's sentence.
Notice
dataclass
¶
Server
¶
The CLI's MCP server for one call, and the directories it may read and write.
Attributes:
| Name | Type | Description |
|---|---|---|
session |
The MCP session, initialized. |
|
read_root |
Where the call's source is written, for the CLI to open. |
|
write_root |
Where the CLI saves what it is asked to. |
source
¶
Write the call's source into the read root, and return its path.
saved
¶
The bytes the CLI saved at path, refused as too large past the limit.
call
async
¶
call(tool: str, **arguments: JsonValue) -> CallToolResult
Call a tool, and refuse with its reply's text if it failed.
text
async
¶
Call a tool, and return its reply's text.
Spawn
¶
Spawn(
craft: Craft,
cli: str | Path,
*,
version: str,
arguments: Sequence[str] = (),
trusted: bool = False,
limit: int,
)
Starts a crafting app's CLI as an MCP server on stdio, once for each call.
The CLI's file access is confined to the call's own directory by its root flags. An app whose
CLI confines nothing (its descriptor's roots is none) reads and writes wherever this
process's user can, so it is started only when the operator says the files are trusted, or
that this process is already confined, as the sandboxed worker is. Either way it is not a
sandbox: the CLI runs as this process's user, with its network.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
craft
|
Craft
|
The app. |
required |
cli
|
str | Path
|
Its CLI, |
required |
version
|
str
|
The version it must be, which provenance records: a CLI of another version is refused. |
required |
arguments
|
Sequence[str]
|
What the CLI is given after |
()
|
trusted
|
bool
|
Whether the files are trusted, or this process confined: required of an app whose CLI confines nothing. |
False
|
limit
|
int
|
The most bytes a saved file may hold. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the app's CLI confines nothing and |
ToolsDialect
¶
ToolsDialect(
cli: str | Path,
*,
version: str,
arguments: Sequence[str] = (),
trusted: bool = False,
limit: int = 256 << 20,
lookup: float = 10.0,
)
Bases: Headless
An app whose CLI's MCP server has a tool for each step, spawned for every call.
A subclass names its app's craft, its files' extension, its tools, and how it reads a
file, inspection.
open
async
¶
Open the source with the open tool, or make a blank file with the new tool.
The server holds one file, so its tools name none: None.
batch
async
¶
batch(
server: Server,
document: JsonValue,
commands: Sequence[AppCommand],
) -> None
Run the commands as one batch, refused whole, naming the command that failed.
save
async
¶
Save the file into the write root with the save tool, and return its bytes.
page
async
¶
Render one page with the render tool, at scale pixels per point.
find
async
¶
find(server: Server, query: str) -> Sequence[CommandSpec]
The commands the list tool finds for the query, ten at most.
artifactr.files.wordcraft¶
WordCraft's adapter: .docx files edited by WordCraft's own commands, run headless.
Subprocess spawns WordCraft's CLI on this machine, a
fresh MCP server on stdio for every call, for tests and trusted files. The fingerprint is read
from the file itself, by fingerprint: the digest of
its parts without the one each save stamps (lattice RFC-0003, phase 2).
STAMPED
module-attribute
¶
The parts every save stamps, which the fingerprint leaves out.
Subprocess
¶
Subprocess(
cli: str | Path,
*,
version: str,
author: str = "lattice",
limit: int = 256 << 20,
lookup: float = 10.0,
)
Bases: ToolsDialect
WordCraft's headless MCP server, spawned from its CLI on this machine for every call.
It confines the CLI's file access to the call's own directory, but it is not a sandbox: the CLI runs as this process's user, with its network. It is for tests and trusted files; the sandboxed worker runs hostile ones (lattice RFC-0003).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cli
|
str | Path
|
The |
required |
version
|
str
|
The WordCraft version it must be, which provenance records: a call to a CLI of another version is refused. |
required |
author
|
str
|
Who the CLI says tracked changes, comments and saves are by. |
'lattice'
|
limit
|
int
|
The most bytes a file may hold, saved or inflated: 256 MiB by default. |
256 << 20
|
lookup
|
float
|
How long, in seconds, finding commands may take: 10 by default. |
10.0
|
inspection
async
¶
inspection(
server: Server, document: JsonValue, content: bytes
) -> Inspection
The document's text, headings, comments, tracked changes, pages and fingerprint.
fingerprint
¶
The digest of a .docx's parts, by name, without the parts each save stamps.
Each part is hashed on its own, and the fingerprint is the digest of the parts' names and
digests, so no two different files share one. Each part is read as it inflates, so a zip
bomb is refused once it passes limit.
Raises:
| Type | Description |
|---|---|
AppFailed
|
|
media_type
¶
The format a package says it is, or None if it says none.
A Word package says it by its main part's content type, and an OpenDocument by its
mimetype. Call it once fingerprint has read the package, which refuses one that isn't
a zip or inflates past its limit.
Raises:
| Type | Description |
|---|---|
AppFailed
|
|
artifactr.files.native¶
Native files in workspaces: NativeFiles.
Uploads, a person's check-out and check-in, an agent's edit by the app's commands, reads of a revision's bytes, and the collection of bytes nothing names.
Part
module-attribute
¶
Part = Literal['content', 'text', 'review']
What of a revision to read: the file, its words, or its comments and tracked changes.
ContentUnavailable
¶
ContentUnavailable(artifact_id: ArtifactId, part: Part)
Bases: InvalidState
A manifest names bytes the blob store doesn't have.
Every surface can commit edit_artifact, so a hand-written patch can name a digest that was
never put. Reads show the content as unavailable, and edits are refused until it is replaced.
FormatChanged
¶
FormatChanged(
artifact_id: ArtifactId,
*,
checked_out: str,
checked_in: str,
)
Bases: InvalidState
A check-in is in another format than the file checked out, which is never converted.
An editor can save a file in another format than it opened, such as a workbook opened from
.csv saved as .xlsx, or a layout opened from INDD saved as IDML. The head keeps its
format: check the file in as a new one instead (native files ADR-0012).
NativeFiles
¶
NativeFiles(
blobs: Blobs,
checkouts: Checkouts,
app: App | Mapping[type[NativeFile], App],
*,
checkout_ttl: timedelta = timedelta(hours=8),
deadline: float = 30.0,
edit_ttl: timedelta = timedelta(seconds=45),
keep: timedelta = timedelta(days=1),
clock: Clock = utc_now,
integrations: Integrations | None = None,
)
Native files in workspaces: their bytes, their check-outs and the apps that read them.
Every method takes the Workspace to act in, as the actor
who acts, and reads and writes through it, so authorize and the hiding of restricted
artifacts apply as for any read or command.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
blobs
|
Blobs
|
Where the files' bytes are kept. |
required |
checkouts
|
Checkouts
|
Where check-outs are kept. |
required |
app
|
App | Mapping[type[NativeFile], App]
|
The app that reads every native file and runs agents' commands on it; or, for several, the app for each native type, a subclass's own or else its nearest base's. |
required |
checkout_ttl
|
timedelta
|
How long a check-out lasts, unless it is extended: eight hours by default. |
timedelta(hours=8)
|
deadline
|
float
|
How long, in seconds, a call to the app may take: 30 by default. |
30.0
|
edit_ttl
|
timedelta
|
How long an agent's edit holds the lease: the app's deadline and the commit, 45 seconds by default. It is never renewed: an edit that outlives it is still refused at commit, by its base version and its test, if someone else committed meanwhile. |
timedelta(seconds=45)
|
keep
|
timedelta
|
How long bytes no revision names are kept after they were last put, for the commit that follows a put: a day by default. |
timedelta(days=1)
|
clock
|
Clock
|
Returns the current time, which collection keeps bytes from. |
utc_now
|
integrations
|
Integrations | None
|
Whether a file's app's integration, |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If an edit's lease would end before the apps' deadline, or no app is given. |
app_of
¶
app_of(file_type: type[NativeFile]) -> App
The app that reads a native type: its own, or else its nearest base's.
Raises:
| Type | Description |
|---|---|
ValidationFailed
|
If no app reads it. |
enabled
async
¶
enabled(
ws: Workspace, file_type: type[NativeFile] | None = None
) -> bool
Whether the app of a native type is on in the workspace; or, without one, any app.
A type no app reads is off.
key_of
¶
The integration that governs an artifact: its app's for a native file, else None.
It is the key_of of artifactr's integration_gate, so that while a file's app is off,
an edit of the file is refused on every surface, REST and MCP too, not only here. A
native type no app reads is governed by none, and no app edits it.
upload
async
¶
upload(
ws: Workspace,
file_type: type[F],
name: str,
data: AsyncIterable[bytes],
*,
media_type: str,
artifact_id: ArtifactId | None = None,
thread_id: ThreadId | None = None,
) -> Applied | Proposed
Create a native file from uploaded bytes, with a new id unless one is given.
Raises:
| Type | Description |
|---|---|
NotEnabled
|
If the app's integration is off in the workspace. |
ValidationFailed
|
If the type doesn't hold files of |
TooLarge
|
If the file is larger than the blob store's limit. |
AppFailed
|
If the app can't read it. |
check_out
async
¶
check_out(
ws: Workspace, artifact_id: ArtifactId
) -> Checkout
Check a native file out to the workspace's actor, holding its lease.
The lease is taken before the head is read, so the version checked out is the one the lease holds the file at.
Raises:
| Type | Description |
|---|---|
NotEnabled
|
If the app's integration is off in the workspace. |
ArtifactBusy
|
If someone else has the file: a check-out, or an agent's edit. |
NotFound
|
If there is no such native file, or it is hidden from the actor. |
extend
async
¶
Extend a check-out by its whole length again, from now.
Raises:
| Type | Description |
|---|---|
NotEnabled
|
If the app's integration is off in the workspace. |
NotFound
|
If there is no such check-out. |
Forbidden
|
If it is someone else's. |
ArtifactBusy
|
If its lease ended and someone else has the file now. |
InvalidState
|
If it was checked in. |
check_in
async
¶
check_in(
ws: Workspace,
checkout_id: str,
data: AsyncIterable[bytes],
*,
summary: str | None = None,
media_type: str | None = None,
) -> Applied | Proposed | None
Check a file in: a revision from the version checked out, and the lease released.
The base is the check-out's, never the upload's. The lease is taken again first, which renews one that lapsed if no one else took it. A file whose fingerprint is the head's commits nothing, so a check-in retried after it succeeded does nothing. A check-in that is stale, because its lease lapsed and someone else took the file or committed meanwhile, is kept as a proposal against the head, which tests the head's digest and records the check-out's base as its provenance. Either way the check-out ends: its record is closed and its lease released.
The file must be in the format checked out: one that says it is another, by
media_type, or that the app reads as another, is refused, and never converted.
Returns:
| Type | Description |
|---|---|
Applied | Proposed | None
|
The revision's outcome, a proposal's if it was stale, or None if nothing changed. |
Raises:
| Type | Description |
|---|---|
NotEnabled
|
If the app's integration is off in the workspace. |
NotFound
|
If there is no such check-out, or its file is hidden from the actor. |
Forbidden
|
If it is someone else's. |
FormatChanged
|
If the file is in another format than the one checked out. |
InvalidState
|
If it was checked in already, and this file is another. |
TooLarge
|
If the file is larger than the blob store's limit. |
AppFailed
|
If the app can't read it. |
edit
async
¶
edit(
ws: Workspace,
artifact_id: ArtifactId,
*,
base_version: int,
commands: Sequence[AppCommand],
summary: str,
thread_id: ThreadId | None = None,
) -> Applied | Proposed | None
Edit a native file by a batch of its app's commands, holding its lease meanwhile.
For an agent, under the type's propose write policy, the revision is a proposal.
Returns:
| Type | Description |
|---|---|
Applied | Proposed | None
|
The revision's outcome, or None if the batch changed nothing. |
Raises:
| Type | Description |
|---|---|
NotEnabled
|
If the app's integration is off in the workspace. |
NotFound
|
If there is no such native file, or it is hidden from the actor. |
VersionConflict
|
If |
ArtifactBusy
|
If someone else has the file, such as a person's check-out. The agent plans again after the check-in, or at once if the busy lease is None. |
ContentUnavailable
|
If the head's bytes aren't in the blob store. |
AppFailed
|
If the app can't run the batch. |
read
async
¶
read(
ws: Workspace,
artifact_id: ArtifactId,
*,
version: int | None = None,
part: Part = "content",
) -> bytes
Return a native file's bytes, its text or its review, at its head or a version.
The artifact is resolved through the workspace first, which asks authorize and hides
restricted artifacts, and only then are the bytes its revision names read.
Raises:
| Type | Description |
|---|---|
NotFound
|
If there is no such native file or version, or the file is hidden. |
ContentUnavailable
|
If the revision names nothing for |
render
async
¶
render(
ws: Workspace,
artifact_id: ArtifactId,
page: int,
*,
version: int | None = None,
) -> bytes
Render one page of a native file, from 1, at its head or a version, as a PNG.
Raises:
| Type | Description |
|---|---|
NotFound
|
If there is no such native file, or it is hidden. |
ContentUnavailable
|
If the blob store doesn't have its bytes. |
AppFailed
|
If the app can't render it, or there is no such page. |
proposed
async
¶
proposed(
ws: Workspace,
proposal_id: ProposalId,
*,
part: Part = "content",
) -> bytes
Return what a proposal would make of a native file: its bytes, its text or its review.
A stale check-in is kept as a proposal, and its reviewer reads it beside the head. The proposal is read through the workspace, and then its file, as a read of either is, so a hidden one is answered as a missing one.
Raises:
| Type | Description |
|---|---|
NotFound
|
If there is no such proposal, it is to no native file, or either is hidden. |
ContentUnavailable
|
If the proposal names nothing for |
render_proposed
async
¶
render_proposed(
ws: Workspace, proposal_id: ProposalId, page: int
) -> bytes
Render one page of what a proposal would make of a native file, from 1, as a PNG.
Raises:
| Type | Description |
|---|---|
NotFound
|
If there is no such proposal, it is to no native file, or either is hidden. |
ContentUnavailable
|
If the proposal names no content, or the blob store hasn't it. |
AppFailed
|
If the app can't render it, or there is no such page. |
collect
async
¶
Delete the workspace's blobs that nothing names, unless they were put lately.
Named means by a pending proposal, or by any revision of a native file, archived ones
too; the proposals are read first, so that one accepted meanwhile is read as a
revision. Bytes put within keep are kept whatever names them, for the commit that
follows a put. The handle must see every thread and artifact, as an application's own
actor does: a blob that only a hidden revision names would otherwise be lost.
Returns:
| Type | Description |
|---|---|
int
|
How many blobs were deleted. |
Raises:
| Type | Description |
|---|---|
Forbidden
|
If the handle's actor can't see everything in the workspace. |
artifactr.files.tools¶
The agent's tools for native files, in one toolset.
A pydantic-ai toolset beside artifactr's own, offered only where the app's integration is on: reading a file's words and its review, finding the app's commands, editing by a batch of them, and, if the application turns it on, looking at a page (lattice RFC-0003, phase 3).
REVIEW_ITEMS
module-attribute
¶
REVIEW_ITEMS: Final = 50
The most comments and tracked changes one read_review returns.
TEXT_CHARS
module-attribute
¶
TEXT_CHARS: Final = 8000
The most of a file's words one read_native returns.
native_file_tools
¶
native_file_tools(
files: NativeFiles, *, view: bool = False
) -> AbstractToolset[Session[Any]]
Build the toolset for native files, through files and its app.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
files
|
NativeFiles
|
The native files, and the app that reads and edits them. |
required |
view
|
bool
|
Whether to offer |
False
|
artifactr.files.fastapi¶
People's check-out and check-in over HTTP, with the fastapi extra.
files_router is the REST surface, mounted beside
artifactr's own router: check out, extend, check in, download a version and fetch a page.
editor_app serves an app's browser build, as released,
on an origin of its own, under a policy that lets it reach nothing else (lattice RFC-0003,
phase 4).
CheckedIn
pydantic-model
¶
FileStatus
pydantic-model
¶
Bases: BaseModel
A native file as a person about to check it out sees it.
Fields:
Lease
pydantic-model
¶
Bases: BaseModel
Who has a native file, until when.
Fields:
-
checked_out_by(str | None) -
expires_at(datetime) -
checkout_id(str | None)
editor_app
¶
editor_app(
build: Path,
*,
craft: Craft | None = None,
portal: str | None = None,
methods: Sequence[str] = (),
) -> ASGIApp
Serve a browser build's directory, as released, under its policy; and its shim, if asked.
With craft and portal, it also serves the shim at /.lattice/shim.html, which the
portal frames to open the checked-out file in the build and hear its saves.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
build
|
Path
|
The directory holding the build's |
required |
craft
|
Craft | None
|
The app whose build it is, if its build has a host surface for the shim to reach. |
None
|
portal
|
str | None
|
The portal's origin, the only one that may frame the shim, and that it answers. |
None
|
methods
|
Sequence[str]
|
For a build with a JavaScript API, the methods of it the portal may call. |
()
|
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If the directory has no |
ValueError
|
If only one of |
editor_policy
¶
The Content-Security-Policy for a build whose page is index.
Its own files, and blob: for what the page makes itself, such as a saved file; its inline
scripts by their SHA-256, and WebAssembly compiled from its own bytes, and no other code;
styles; and no form, base or plugin. Nothing frames it; or, given the portal that frames
the shim, only the shim, of its own origin, inside the portal: a frame's ancestors are each
checked, the top one too.
files_router
¶
files_router(
workspaces: Workspaces,
files: NativeFiles,
*,
resolve_actor: ResolveActor,
limit: int = DEFAULT_LIMIT,
) -> APIRouter
Build the router for native files' check-out and check-in, downloads and pages.
Mount it beside artifactr_router, with the same resolve_actor and workspaces.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
workspaces
|
Workspaces
|
Opens tenant-scoped workspaces, and asks their |
required |
files
|
NativeFiles
|
The native files, their stores and their app. |
required |
resolve_actor
|
ResolveActor
|
Authenticates each request, as for artifactr's router. |
required |
limit
|
int
|
The largest check-in, in bytes, refused from its |
DEFAULT_LIMIT
|
artifactr.files.sql¶
Blobs and check-outs in SQL: PostgreSQL or SQLite, through SQLAlchemy 2's asyncio extension.
Install artifactr-ai[files,postgres] (asyncpg) or artifactr-ai[files,sqlite] (aiosqlite).
Upgrade the database with migrate, then give
NativeFiles the stores:
engine = create_async_engine("postgresql+asyncpg://localhost/app")
await migrate(engine)
files = NativeFiles(SqlBlobs(engine), SqlCheckouts(engine), app)
With row-level security, migrate as the tables' owner, granting a role that owns nothing the
tables, and give the stores that role's engine with row_level_security=True, as artifactr's
storage has it (artifactr ADR-0066). For SQLite, create the engine with
create_sqlite_engine.
create_schema creates the tables without migrations, for
tests and prototypes.
SqlBlobs
¶
SqlBlobs(
engine: AsyncEngine,
*,
clock: Clock = utc_now,
limit: int = DEFAULT_LIMIT,
row_level_security: bool = False,
)
The Blobs port in a SQL database.
Create the tables with migrate first. The same code runs on
PostgreSQL and on SQLite, whose engines must come from
create_sqlite_engine.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
engine
|
AsyncEngine
|
The database. The store does not dispose of it. |
required |
clock
|
Clock
|
Returns the current time, which a put is stamped with. |
utc_now
|
limit
|
int
|
The largest blob, in bytes: 25 MiB unless given, and at most 100 MiB. The bytes are held in memory as they arrive, up to it. |
DEFAULT_LIMIT
|
row_level_security
|
bool
|
Whether each transaction names its tenant to PostgreSQL, whose policies, which the migrations create, then show it only that tenant's rows. |
False
|
put
async
¶
put(
scope: Scope,
artifact_id: ArtifactId,
data: AsyncIterable[bytes],
media_type: str,
) -> Blob
Store bytes for an artifact, and name them, as Blobs.put does.
get
async
¶
get(
scope: Scope, artifact_id: ArtifactId, sha256: str
) -> bytes | None
Return an artifact's bytes with this digest, or None if it has none.
collect
async
¶
Delete the workspace's unreferenced blobs last put before before; return how many.
The blobs old enough are read, and those not referenced deleted in batches, each statement checking again that the blob is still old enough: a blob put again meanwhile, for a revision about to be committed, is kept.
SqlCheckouts
¶
SqlCheckouts(
engine: AsyncEngine, *, row_level_security: bool = False
)
create_schema
async
¶
Create the tables straight from the models, for tests and prototypes.
It skips Alembic, so the database records no schema version and cannot be upgraded with
migrate later. Use migrate for databases that last.
migrate
async
¶
migrate(
engine: AsyncEngine,
*,
application_role: str | None = None,
) -> None
Upgrade the database to the native files' latest schema, creating the tables if needed.
Run it when the application deploys or starts, as the tables' owner. It is safe to run on every start: a database that is already up to date is left alone.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
engine
|
AsyncEngine
|
The database, as the tables' owner. |
required |
application_role
|
str | None
|
A role that owns nothing, which the application connects as with row-level security on: it is granted the tables, on PostgreSQL. It must exist. |
None
|
create_sqlite_engine
¶
Create an async SQLite engine whose transactions take turns.
Every transaction on the engine, reads included, holds the database's write lock, so
transactions run one at a time, on the engine's one connection. A transaction waits for the
connection for up to the engine's pool_timeout (30 seconds), and for another process's
lock for up to the timeout connect argument of Python's sqlite3 (5 seconds unless
connect_args says otherwise). Use a database file (or a shared-cache memory database).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str | URL
|
A SQLite URL for an async driver, such as |
required |
**kwargs
|
Any
|
Passed to SQLAlchemy's |
{}
|
Returns:
| Type | Description |
|---|---|
AsyncEngine
|
The engine. Dispose of it when you are done. |