artifactr-ai 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. artifactr_ai-0.2.0/LICENSE +21 -0
  2. artifactr_ai-0.2.0/PKG-INFO +197 -0
  3. artifactr_ai-0.2.0/README.md +136 -0
  4. artifactr_ai-0.2.0/pyproject.toml +174 -0
  5. artifactr_ai-0.2.0/pyproject.toml.orig +143 -0
  6. artifactr_ai-0.2.0/src/artifactr/__init__.py +71 -0
  7. artifactr_ai-0.2.0/src/artifactr/agent/__init__.py +74 -0
  8. artifactr_ai-0.2.0/src/artifactr/agent/capability.py +439 -0
  9. artifactr_ai-0.2.0/src/artifactr/agent/live.py +190 -0
  10. artifactr_ai-0.2.0/src/artifactr/agent/models.py +68 -0
  11. artifactr_ai-0.2.0/src/artifactr/agent/results.py +69 -0
  12. artifactr_ai-0.2.0/src/artifactr/agent/runner.py +1082 -0
  13. artifactr_ai-0.2.0/src/artifactr/agent/session.py +140 -0
  14. artifactr_ai-0.2.0/src/artifactr/agent/tools.py +214 -0
  15. artifactr_ai-0.2.0/src/artifactr/core/__init__.py +337 -0
  16. artifactr_ai-0.2.0/src/artifactr/core/actors.py +187 -0
  17. artifactr_ai-0.2.0/src/artifactr/core/artifacts.py +255 -0
  18. artifactr_ai-0.2.0/src/artifactr/core/commands.py +192 -0
  19. artifactr_ai-0.2.0/src/artifactr/core/errors.py +101 -0
  20. artifactr_ai-0.2.0/src/artifactr/core/events.py +399 -0
  21. artifactr_ai-0.2.0/src/artifactr/core/feedback.py +195 -0
  22. artifactr_ai-0.2.0/src/artifactr/core/ids.py +76 -0
  23. artifactr_ai-0.2.0/src/artifactr/core/live.py +91 -0
  24. artifactr_ai-0.2.0/src/artifactr/core/notes.py +219 -0
  25. artifactr_ai-0.2.0/src/artifactr/core/patches.py +120 -0
  26. artifactr_ai-0.2.0/src/artifactr/core/protocol.py +186 -0
  27. artifactr_ai-0.2.0/src/artifactr/core/rules.py +809 -0
  28. artifactr_ai-0.2.0/src/artifactr/core/schema.py +47 -0
  29. artifactr_ai-0.2.0/src/artifactr/core/state.py +208 -0
  30. artifactr_ai-0.2.0/src/artifactr/core/tagged.py +13 -0
  31. artifactr_ai-0.2.0/src/artifactr/core/visibility.py +174 -0
  32. artifactr_ai-0.2.0/src/artifactr/evals/__init__.py +47 -0
  33. artifactr_ai-0.2.0/src/artifactr/evals/context.py +192 -0
  34. artifactr_ai-0.2.0/src/artifactr/evals/experiments.py +188 -0
  35. artifactr_ai-0.2.0/src/artifactr/evals/measures.py +195 -0
  36. artifactr_ai-0.2.0/src/artifactr/evals/online.py +168 -0
  37. artifactr_ai-0.2.0/src/artifactr/evals/source.py +122 -0
  38. artifactr_ai-0.2.0/src/artifactr/fastapi/__init__.py +20 -0
  39. artifactr_ai-0.2.0/src/artifactr/fastapi/openapi.py +50 -0
  40. artifactr_ai-0.2.0/src/artifactr/fastapi/router.py +248 -0
  41. artifactr_ai-0.2.0/src/artifactr/fastapi/stream.py +250 -0
  42. artifactr_ai-0.2.0/src/artifactr/langfuse/__init__.py +35 -0
  43. artifactr_ai-0.2.0/src/artifactr/langfuse/client.py +26 -0
  44. artifactr_ai-0.2.0/src/artifactr/langfuse/tracing.py +87 -0
  45. artifactr_ai-0.2.0/src/artifactr/litellm/__init__.py +38 -0
  46. artifactr_ai-0.2.0/src/artifactr/litellm/gateway.py +185 -0
  47. artifactr_ai-0.2.0/src/artifactr/mcp/__init__.py +16 -0
  48. artifactr_ai-0.2.0/src/artifactr/mcp/server.py +537 -0
  49. artifactr_ai-0.2.0/src/artifactr/otel/__init__.py +52 -0
  50. artifactr_ai-0.2.0/src/artifactr/otel/configure.py +142 -0
  51. artifactr_ai-0.2.0/src/artifactr/py.typed +0 -0
  52. artifactr_ai-0.2.0/src/artifactr/scores/__init__.py +18 -0
  53. artifactr_ai-0.2.0/src/artifactr/scores/mapping.py +31 -0
  54. artifactr_ai-0.2.0/src/artifactr/scores/mirror.py +186 -0
  55. artifactr_ai-0.2.0/src/artifactr/sql/__init__.py +18 -0
  56. artifactr_ai-0.2.0/src/artifactr/sql/migrations/env.py +17 -0
  57. artifactr_ai-0.2.0/src/artifactr/sql/migrations/script.py.mako +26 -0
  58. artifactr_ai-0.2.0/src/artifactr/sql/migrations/versions/0001_initial.py +144 -0
  59. artifactr_ai-0.2.0/src/artifactr/sql/migrations/versions/0002_event_scopes.py +47 -0
  60. artifactr_ai-0.2.0/src/artifactr/sql/migrations/versions/0003_message_ids.py +53 -0
  61. artifactr_ai-0.2.0/src/artifactr/sql/migrations/versions/0004_cursors.py +35 -0
  62. artifactr_ai-0.2.0/src/artifactr/sql/migrations/versions/0005_upgrade_point.py +53 -0
  63. artifactr_ai-0.2.0/src/artifactr/sql/migrations/versions/0006_row_level_security.py +57 -0
  64. artifactr_ai-0.2.0/src/artifactr/sql/schema.py +66 -0
  65. artifactr_ai-0.2.0/src/artifactr/sql/sqlite.py +18 -0
  66. artifactr_ai-0.2.0/src/artifactr/sql/storage.py +578 -0
  67. artifactr_ai-0.2.0/src/artifactr/sql/tables.py +175 -0
  68. artifactr_ai-0.2.0/src/artifactr/sql/tenancy.py +18 -0
  69. artifactr_ai-0.2.0/src/artifactr/telemetry/__init__.py +59 -0
  70. artifactr_ai-0.2.0/src/artifactr/telemetry/attributes.py +147 -0
  71. artifactr_ai-0.2.0/src/artifactr/telemetry/metrics.py +308 -0
  72. artifactr_ai-0.2.0/src/artifactr/telemetry/recording.py +207 -0
  73. artifactr_ai-0.2.0/src/artifactr/telemetry/spans.py +127 -0
  74. artifactr_ai-0.2.0/src/artifactr/telemetry/traces.py +67 -0
  75. artifactr_ai-0.2.0/src/artifactr/workspace/__init__.py +39 -0
  76. artifactr_ai-0.2.0/src/artifactr/workspace/memory.py +322 -0
  77. artifactr_ai-0.2.0/src/artifactr/workspace/storage.py +287 -0
  78. artifactr_ai-0.2.0/src/artifactr/workspace/workspace.py +1165 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alex Nodeland
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,197 @@
1
+ Metadata-Version: 2.4
2
+ Name: artifactr-ai
3
+ Version: 0.2.0
4
+ Summary: Build chat applications where people and agents collaborate through shared, versioned artifacts.
5
+ Keywords: agents,llm,collaboration,artifacts,pydantic-ai,mcp
6
+ Author: Alex Nodeland
7
+ Author-email: Alex Nodeland <alex@ournature.studio>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Framework :: Pydantic :: 2
12
+ Classifier: Framework :: FastAPI
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
20
+ Classifier: Typing :: Typed
21
+ Requires-Dist: lattice-core-ai>=0.1.0
22
+ Requires-Dist: pydantic>=2.13
23
+ Requires-Dist: jsonpatch>=1.33
24
+ Requires-Dist: jsonpointer>=1.9
25
+ Requires-Dist: pydantic-ai-slim>=2.51
26
+ Requires-Dist: opentelemetry-api>=1.28
27
+ Requires-Dist: evalr>=0.1.0.dev0 ; extra == 'evals'
28
+ Requires-Dist: fastapi>=0.141 ; extra == 'fastapi'
29
+ Requires-Dist: starlette>=0.46.0 ; extra == 'fastapi'
30
+ Requires-Dist: lattice-core-ai[langfuse]>=0.1.0 ; extra == 'langfuse'
31
+ Requires-Dist: langfuse>=4.15,<5 ; extra == 'langfuse'
32
+ Requires-Dist: evalr[langfuse]>=0.1.0.dev0 ; extra == 'langfuse'
33
+ Requires-Dist: lattice-core-ai[litellm]>=0.1.0 ; extra == 'litellm'
34
+ Requires-Dist: mcp>=2.2 ; extra == 'mcp'
35
+ Requires-Dist: starlette>=0.27 ; extra == 'mcp'
36
+ Requires-Dist: lattice-core-ai[otel]>=0.1.0 ; extra == 'otel'
37
+ Requires-Dist: opentelemetry-sdk>=1.45 ; extra == 'otel'
38
+ Requires-Dist: artifactr-ai[sql] ; extra == 'postgres'
39
+ Requires-Dist: asyncpg>=0.31 ; extra == 'postgres'
40
+ Requires-Dist: lattice-core-ai[sql]>=0.1.0 ; extra == 'sql'
41
+ Requires-Dist: sqlalchemy[asyncio]>=2.1 ; extra == 'sql'
42
+ Requires-Dist: alembic>=1.20 ; extra == 'sql'
43
+ Requires-Dist: artifactr-ai[sql] ; extra == 'sqlite'
44
+ Requires-Dist: aiosqlite>=0.22 ; extra == 'sqlite'
45
+ Requires-Python: >=3.12
46
+ Project-URL: Homepage, https://lattice.alexnodeland.com/artifactr/
47
+ Project-URL: Documentation, https://lattice.alexnodeland.com/artifactr/
48
+ Project-URL: Repository, https://github.com/alexnodeland/lattice
49
+ Project-URL: Issues, https://github.com/alexnodeland/lattice/issues
50
+ Project-URL: Changelog, https://github.com/alexnodeland/lattice/blob/main/packages/artifactr/CHANGELOG.md
51
+ Provides-Extra: evals
52
+ Provides-Extra: fastapi
53
+ Provides-Extra: langfuse
54
+ Provides-Extra: litellm
55
+ Provides-Extra: mcp
56
+ Provides-Extra: otel
57
+ Provides-Extra: postgres
58
+ Provides-Extra: sql
59
+ Provides-Extra: sqlite
60
+ Description-Content-Type: text/markdown
61
+
62
+ <picture>
63
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/alexnodeland/lattice/main/docs/artifactr/assets/brand/banner-dark.svg">
64
+ <img alt="artifactr: people and agents editing the same artifacts, every change versioned and attributed." src="https://raw.githubusercontent.com/alexnodeland/lattice/main/docs/artifactr/assets/brand/banner-light.svg" width="100%">
65
+ </picture>
66
+
67
+ <p>
68
+ <a href="https://lattice.alexnodeland.com/artifactr/"><img alt="Docs" src="https://img.shields.io/badge/docs-lattice.alexnodeland.com%2Fartifactr-2D2A8C"></a>
69
+ <a href="https://github.com/alexnodeland/lattice/actions/workflows/nightly.yml"><img alt="Nightly" src="https://github.com/alexnodeland/lattice/actions/workflows/nightly.yml/badge.svg?branch=main"></a>
70
+ <img alt="Python 3.12, 3.13 and 3.14" src="https://img.shields.io/badge/python-3.12%20%7C%203.13%20%7C%203.14-2D2A8C">
71
+ <img alt="Coverage: 100%" src="https://img.shields.io/badge/coverage-100%25-2D2A8C">
72
+ <img alt="Typed: pyright strict" src="https://img.shields.io/badge/typed-pyright%20strict-2D2A8C">
73
+ <a href="https://github.com/alexnodeland/lattice/blob/main/packages/artifactr/LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-2D2A8C"></a>
74
+ </p>
75
+
76
+ **artifactr** is a Python library for chat applications in which people and agents work on the same documents, plans and specs, with every change versioned, attributed and fed back into the agent's context.
77
+
78
+ > **Status:** alpha. [0.1.0](https://github.com/alexnodeland/artifactr/releases/tag/v0.1.0) is the first release and delivers [RFC-0001](https://lattice.alexnodeland.com/artifactr/rfcs/0001-v0.1-implementation-plan/), the v0.1 plan. The API may still change before 1.0.
79
+
80
+ ## Why
81
+
82
+ In most chat applications the conversation is the only channel. artifactr adds a second: artifacts that live beside the chat and that both sides edit directly. When a person rewrites a paragraph the agent drafted, or the agent restructures a plan, the edit says something about how each side is thinking. So every edit is versioned, attributed to whoever made it, visible to every participant, and told to the agent before it acts.
83
+
84
+ ## Install
85
+
86
+ Python 3.12 or newer. artifactr is distributed as **`artifactr-ai`** and imported as `artifactr` (the name `artifactr` on PyPI is an unrelated project). Until it is on PyPI, install it from its directory in [lattice](https://github.com/alexnodeland/lattice), the repository every package lives in:
87
+
88
+ ```bash
89
+ uv add "artifactr-ai[fastapi] @ git+https://github.com/alexnodeland/lattice#subdirectory=packages/artifactr"
90
+ ```
91
+
92
+ Extras: `fastapi` (WebSocket and REST), `mcp` (external agents), `postgres` or `sqlite` (SQL storage with a driver), and `sql` (SQL storage without one).
93
+
94
+ ## Example
95
+
96
+ A person edits a brief, then asks the agent for more. The agent is told about the edit before it starts, and its own edit is recorded the same way:
97
+
98
+ ```python
99
+ import asyncio
100
+
101
+ from pydantic_ai import Agent
102
+
103
+ from artifactr import (
104
+ ArtifactWorkspace,
105
+ InMemoryStorage,
106
+ MarkdownArtifact,
107
+ Runner,
108
+ Session,
109
+ UserActor,
110
+ Workspaces,
111
+ )
112
+ from artifactr.core import SetFocus
113
+
114
+
115
+ class Brief(MarkdownArtifact): # an artifact type: a Pydantic model, registered as "brief"
116
+ pass
117
+
118
+
119
+ agent = Agent(
120
+ "anthropic:claude-sonnet-5-5",
121
+ deps_type=Session[None],
122
+ capabilities=[ArtifactWorkspace(types=[Brief])], # tools, instructions and change notes
123
+ )
124
+ runner = Runner(agent, app=None)
125
+ workspaces = Workspaces(InMemoryStorage(), types=[Brief])
126
+
127
+
128
+ async def main() -> None:
129
+ alice = UserActor(id="u_alice", name="Alice")
130
+ ws = await workspaces.open("acme", "launch", actor=alice)
131
+ thread = await ws.create_thread("Launch brief")
132
+
133
+ await ws.create(Brief(text="# Launch\n\nWe ship on Friday."), artifact_id="brief")
134
+ await ws.commit(SetFocus(thread_id=thread.id, artifact_ids=("brief",)))
135
+
136
+ brief = await ws.get(Brief, "brief")
137
+ await ws.commit(brief.edit_text("Friday", "Monday")) # version 2, by Alice
138
+
139
+ sent = await runner.send(ws, thread.id, "Add a risks section to the brief.")
140
+ if sent.run is not None:
141
+ await sent.run.wait() # the agent is told what Alice changed before it starts
142
+
143
+ for envelope in await ws.read():
144
+ print(envelope.seq, envelope.actor.kind, envelope.event.type)
145
+
146
+
147
+ asyncio.run(main())
148
+ ```
149
+
150
+ It needs `ANTHROPIC_API_KEY`, or any other [pydantic-ai model](https://ai.pydantic.dev/models/). The log it prints shows Alice's edit, the agent's run, its tool call and its own edit, in order and attributed.
151
+
152
+ ## Try it
153
+
154
+ [`examples/docplan`](https://github.com/alexnodeland/lattice/blob/main/examples/docplan/README.md) is a complete application built on the library: a person and an agent co-write a document and plan its work, over WebSocket, REST and MCP, with a terminal client. From the root of a clone of lattice:
155
+
156
+ ```sh
157
+ make install
158
+ export ANTHROPIC_API_KEY=...
159
+ uv run docplan-serve # in one terminal
160
+ uv run docplan --user alice # in another
161
+ ```
162
+
163
+ Set `DOCPLAN_DATABASE_URL` (for example `sqlite+aiosqlite:///docplan.db`) to keep its workspaces in a database instead of memory.
164
+
165
+ ## What you get
166
+
167
+ - **Artifact types as Pydantic models.** Subclass `Artifact`; versioning, validation, patches, change summaries and agent tools come from the library.
168
+ - **One write path.** Every change, from any participant and any surface, is a command committed through a tenant-scoped workspace handle, checked against the artifact's version and appended to the workspace's ordered log.
169
+ - **Proposals that don't block.** Artifact types can make agents propose instead of edit; people accept, edit or reject whenever they like, and the agent hears the outcome.
170
+ - **An agent that follows along.** One pydantic-ai capability adds the artifact tools, instructions rendered fresh for every request, notes on what others changed, steering mid-run, and pauses for questions and approvals.
171
+ - **Live output apart from history.** Token-level output streams to watchers; the log holds only durable facts.
172
+ - **Surfaces that behave the same.** A WebSocket thread protocol and REST for your frontend, and MCP for external agents, all through one command handler.
173
+ - **Storage in memory, SQLite or PostgreSQL**, behind one protocol.
174
+ - **Rules you can read and port.** A pure, synchronous core pinned by language-neutral conformance fixtures, with 100% branch coverage and pyright strict.
175
+
176
+ ## Documentation
177
+
178
+ The documentation is artifactr's section of lattice's site, at **<https://lattice.alexnodeland.com/artifactr/>**. It is built from [`docs/artifactr/`](https://github.com/alexnodeland/lattice/tree/main/docs/artifactr) and published from `main` on every push; run `moon run lattice:docs-serve` to read it locally at <http://localhost:8000>.
179
+
180
+ - [Getting started](https://lattice.alexnodeland.com/artifactr/getting-started/) and the [guides](https://lattice.alexnodeland.com/artifactr/guides/artifact-types/): artifact types, workspaces, storage, the agent, live output, serving, MCP, security and testing.
181
+ - [Architecture](https://lattice.alexnodeland.com/artifactr/architecture/): concepts, layers, the write path, the agent, tenancy and concurrency.
182
+ - [Thread protocol v1](https://lattice.alexnodeland.com/artifactr/protocol/): the WebSocket, REST and MCP contracts, with a generated [JSON Schema](https://github.com/alexnodeland/lattice/blob/main/packages/artifactr/schemas/artifactr.v1.json).
183
+ - [Architecture decision records](https://lattice.alexnodeland.com/artifactr/adr/): why each part is the way it is.
184
+ - [RFCs](https://lattice.alexnodeland.com/artifactr/rfcs/): proposals and the v0.1 build plan.
185
+ - [Brand](https://lattice.alexnodeland.com/artifactr/assets/brand/): the mark, colours and type.
186
+
187
+ ## Built on
188
+
189
+ [Pydantic](https://docs.pydantic.dev), [pydantic-ai](https://ai.pydantic.dev), [SQLAlchemy](https://www.sqlalchemy.org), [FastAPI](https://fastapi.tiangolo.com) and the [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk).
190
+
191
+ ## Contributing
192
+
193
+ See [CONTRIBUTING.md](https://github.com/alexnodeland/lattice/blob/main/CONTRIBUTING.md) for setup, the trunk-based workflow, and the RFC and ADR process.
194
+
195
+ ## License
196
+
197
+ [MIT](https://github.com/alexnodeland/lattice/blob/main/packages/artifactr/LICENSE)
@@ -0,0 +1,136 @@
1
+ <picture>
2
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/alexnodeland/lattice/main/docs/artifactr/assets/brand/banner-dark.svg">
3
+ <img alt="artifactr: people and agents editing the same artifacts, every change versioned and attributed." src="https://raw.githubusercontent.com/alexnodeland/lattice/main/docs/artifactr/assets/brand/banner-light.svg" width="100%">
4
+ </picture>
5
+
6
+ <p>
7
+ <a href="https://lattice.alexnodeland.com/artifactr/"><img alt="Docs" src="https://img.shields.io/badge/docs-lattice.alexnodeland.com%2Fartifactr-2D2A8C"></a>
8
+ <a href="https://github.com/alexnodeland/lattice/actions/workflows/nightly.yml"><img alt="Nightly" src="https://github.com/alexnodeland/lattice/actions/workflows/nightly.yml/badge.svg?branch=main"></a>
9
+ <img alt="Python 3.12, 3.13 and 3.14" src="https://img.shields.io/badge/python-3.12%20%7C%203.13%20%7C%203.14-2D2A8C">
10
+ <img alt="Coverage: 100%" src="https://img.shields.io/badge/coverage-100%25-2D2A8C">
11
+ <img alt="Typed: pyright strict" src="https://img.shields.io/badge/typed-pyright%20strict-2D2A8C">
12
+ <a href="https://github.com/alexnodeland/lattice/blob/main/packages/artifactr/LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-2D2A8C"></a>
13
+ </p>
14
+
15
+ **artifactr** is a Python library for chat applications in which people and agents work on the same documents, plans and specs, with every change versioned, attributed and fed back into the agent's context.
16
+
17
+ > **Status:** alpha. [0.1.0](https://github.com/alexnodeland/artifactr/releases/tag/v0.1.0) is the first release and delivers [RFC-0001](https://lattice.alexnodeland.com/artifactr/rfcs/0001-v0.1-implementation-plan/), the v0.1 plan. The API may still change before 1.0.
18
+
19
+ ## Why
20
+
21
+ In most chat applications the conversation is the only channel. artifactr adds a second: artifacts that live beside the chat and that both sides edit directly. When a person rewrites a paragraph the agent drafted, or the agent restructures a plan, the edit says something about how each side is thinking. So every edit is versioned, attributed to whoever made it, visible to every participant, and told to the agent before it acts.
22
+
23
+ ## Install
24
+
25
+ Python 3.12 or newer. artifactr is distributed as **`artifactr-ai`** and imported as `artifactr` (the name `artifactr` on PyPI is an unrelated project). Until it is on PyPI, install it from its directory in [lattice](https://github.com/alexnodeland/lattice), the repository every package lives in:
26
+
27
+ ```bash
28
+ uv add "artifactr-ai[fastapi] @ git+https://github.com/alexnodeland/lattice#subdirectory=packages/artifactr"
29
+ ```
30
+
31
+ Extras: `fastapi` (WebSocket and REST), `mcp` (external agents), `postgres` or `sqlite` (SQL storage with a driver), and `sql` (SQL storage without one).
32
+
33
+ ## Example
34
+
35
+ A person edits a brief, then asks the agent for more. The agent is told about the edit before it starts, and its own edit is recorded the same way:
36
+
37
+ ```python
38
+ import asyncio
39
+
40
+ from pydantic_ai import Agent
41
+
42
+ from artifactr import (
43
+ ArtifactWorkspace,
44
+ InMemoryStorage,
45
+ MarkdownArtifact,
46
+ Runner,
47
+ Session,
48
+ UserActor,
49
+ Workspaces,
50
+ )
51
+ from artifactr.core import SetFocus
52
+
53
+
54
+ class Brief(MarkdownArtifact): # an artifact type: a Pydantic model, registered as "brief"
55
+ pass
56
+
57
+
58
+ agent = Agent(
59
+ "anthropic:claude-sonnet-5-5",
60
+ deps_type=Session[None],
61
+ capabilities=[ArtifactWorkspace(types=[Brief])], # tools, instructions and change notes
62
+ )
63
+ runner = Runner(agent, app=None)
64
+ workspaces = Workspaces(InMemoryStorage(), types=[Brief])
65
+
66
+
67
+ async def main() -> None:
68
+ alice = UserActor(id="u_alice", name="Alice")
69
+ ws = await workspaces.open("acme", "launch", actor=alice)
70
+ thread = await ws.create_thread("Launch brief")
71
+
72
+ await ws.create(Brief(text="# Launch\n\nWe ship on Friday."), artifact_id="brief")
73
+ await ws.commit(SetFocus(thread_id=thread.id, artifact_ids=("brief",)))
74
+
75
+ brief = await ws.get(Brief, "brief")
76
+ await ws.commit(brief.edit_text("Friday", "Monday")) # version 2, by Alice
77
+
78
+ sent = await runner.send(ws, thread.id, "Add a risks section to the brief.")
79
+ if sent.run is not None:
80
+ await sent.run.wait() # the agent is told what Alice changed before it starts
81
+
82
+ for envelope in await ws.read():
83
+ print(envelope.seq, envelope.actor.kind, envelope.event.type)
84
+
85
+
86
+ asyncio.run(main())
87
+ ```
88
+
89
+ It needs `ANTHROPIC_API_KEY`, or any other [pydantic-ai model](https://ai.pydantic.dev/models/). The log it prints shows Alice's edit, the agent's run, its tool call and its own edit, in order and attributed.
90
+
91
+ ## Try it
92
+
93
+ [`examples/docplan`](https://github.com/alexnodeland/lattice/blob/main/examples/docplan/README.md) is a complete application built on the library: a person and an agent co-write a document and plan its work, over WebSocket, REST and MCP, with a terminal client. From the root of a clone of lattice:
94
+
95
+ ```sh
96
+ make install
97
+ export ANTHROPIC_API_KEY=...
98
+ uv run docplan-serve # in one terminal
99
+ uv run docplan --user alice # in another
100
+ ```
101
+
102
+ Set `DOCPLAN_DATABASE_URL` (for example `sqlite+aiosqlite:///docplan.db`) to keep its workspaces in a database instead of memory.
103
+
104
+ ## What you get
105
+
106
+ - **Artifact types as Pydantic models.** Subclass `Artifact`; versioning, validation, patches, change summaries and agent tools come from the library.
107
+ - **One write path.** Every change, from any participant and any surface, is a command committed through a tenant-scoped workspace handle, checked against the artifact's version and appended to the workspace's ordered log.
108
+ - **Proposals that don't block.** Artifact types can make agents propose instead of edit; people accept, edit or reject whenever they like, and the agent hears the outcome.
109
+ - **An agent that follows along.** One pydantic-ai capability adds the artifact tools, instructions rendered fresh for every request, notes on what others changed, steering mid-run, and pauses for questions and approvals.
110
+ - **Live output apart from history.** Token-level output streams to watchers; the log holds only durable facts.
111
+ - **Surfaces that behave the same.** A WebSocket thread protocol and REST for your frontend, and MCP for external agents, all through one command handler.
112
+ - **Storage in memory, SQLite or PostgreSQL**, behind one protocol.
113
+ - **Rules you can read and port.** A pure, synchronous core pinned by language-neutral conformance fixtures, with 100% branch coverage and pyright strict.
114
+
115
+ ## Documentation
116
+
117
+ The documentation is artifactr's section of lattice's site, at **<https://lattice.alexnodeland.com/artifactr/>**. It is built from [`docs/artifactr/`](https://github.com/alexnodeland/lattice/tree/main/docs/artifactr) and published from `main` on every push; run `moon run lattice:docs-serve` to read it locally at <http://localhost:8000>.
118
+
119
+ - [Getting started](https://lattice.alexnodeland.com/artifactr/getting-started/) and the [guides](https://lattice.alexnodeland.com/artifactr/guides/artifact-types/): artifact types, workspaces, storage, the agent, live output, serving, MCP, security and testing.
120
+ - [Architecture](https://lattice.alexnodeland.com/artifactr/architecture/): concepts, layers, the write path, the agent, tenancy and concurrency.
121
+ - [Thread protocol v1](https://lattice.alexnodeland.com/artifactr/protocol/): the WebSocket, REST and MCP contracts, with a generated [JSON Schema](https://github.com/alexnodeland/lattice/blob/main/packages/artifactr/schemas/artifactr.v1.json).
122
+ - [Architecture decision records](https://lattice.alexnodeland.com/artifactr/adr/): why each part is the way it is.
123
+ - [RFCs](https://lattice.alexnodeland.com/artifactr/rfcs/): proposals and the v0.1 build plan.
124
+ - [Brand](https://lattice.alexnodeland.com/artifactr/assets/brand/): the mark, colours and type.
125
+
126
+ ## Built on
127
+
128
+ [Pydantic](https://docs.pydantic.dev), [pydantic-ai](https://ai.pydantic.dev), [SQLAlchemy](https://www.sqlalchemy.org), [FastAPI](https://fastapi.tiangolo.com) and the [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk).
129
+
130
+ ## Contributing
131
+
132
+ See [CONTRIBUTING.md](https://github.com/alexnodeland/lattice/blob/main/CONTRIBUTING.md) for setup, the trunk-based workflow, and the RFC and ADR process.
133
+
134
+ ## License
135
+
136
+ [MIT](https://github.com/alexnodeland/lattice/blob/main/packages/artifactr/LICENSE)
@@ -0,0 +1,174 @@
1
+ [project]
2
+ name = "artifactr-ai"
3
+ version = "0.2.0"
4
+ description = "Build chat applications where people and agents collaborate through shared, versioned artifacts."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.12"
9
+ keywords = [
10
+ "agents",
11
+ "llm",
12
+ "collaboration",
13
+ "artifacts",
14
+ "pydantic-ai",
15
+ "mcp",
16
+ ]
17
+ classifiers = [
18
+ "Development Status :: 3 - Alpha",
19
+ "Framework :: Pydantic :: 2",
20
+ "Framework :: FastAPI",
21
+ "Intended Audience :: Developers",
22
+ "Operating System :: OS Independent",
23
+ "Programming Language :: Python :: 3 :: Only",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Programming Language :: Python :: 3.14",
27
+ "Topic :: Software Development :: Libraries :: Application Frameworks",
28
+ "Typing :: Typed",
29
+ ]
30
+ dependencies = [
31
+ "lattice-core-ai>=0.1.0",
32
+ "pydantic>=2.13",
33
+ "jsonpatch>=1.33",
34
+ "jsonpointer>=1.9",
35
+ "pydantic-ai-slim>=2.51",
36
+ "opentelemetry-api>=1.28",
37
+ ]
38
+
39
+ [[project.authors]]
40
+ name = "Alex Nodeland"
41
+ email = "alex@ournature.studio"
42
+
43
+ [project.optional-dependencies]
44
+ sql = [
45
+ "lattice-core-ai[sql]>=0.1.0",
46
+ "sqlalchemy[asyncio]>=2.1",
47
+ "alembic>=1.20",
48
+ ]
49
+ sqlite = [
50
+ "artifactr-ai[sql]",
51
+ "aiosqlite>=0.22",
52
+ ]
53
+ postgres = [
54
+ "artifactr-ai[sql]",
55
+ "asyncpg>=0.31",
56
+ ]
57
+ fastapi = [
58
+ "fastapi>=0.141",
59
+ "starlette>=0.46.0",
60
+ ]
61
+ mcp = [
62
+ "mcp>=2.2",
63
+ "starlette>=0.27",
64
+ ]
65
+ otel = [
66
+ "lattice-core-ai[otel]>=0.1.0",
67
+ "opentelemetry-sdk>=1.45",
68
+ ]
69
+ langfuse = [
70
+ "lattice-core-ai[langfuse]>=0.1.0",
71
+ "langfuse>=4.15,<5",
72
+ "evalr[langfuse]>=0.1.0.dev0",
73
+ ]
74
+ litellm = ["lattice-core-ai[litellm]>=0.1.0"]
75
+ evals = ["evalr>=0.1.0.dev0"]
76
+
77
+ [project.urls]
78
+ Homepage = "https://lattice.alexnodeland.com/artifactr/"
79
+ Documentation = "https://lattice.alexnodeland.com/artifactr/"
80
+ Repository = "https://github.com/alexnodeland/lattice"
81
+ Issues = "https://github.com/alexnodeland/lattice/issues"
82
+ Changelog = "https://github.com/alexnodeland/lattice/blob/main/packages/artifactr/CHANGELOG.md"
83
+
84
+ [dependency-groups]
85
+ test = [
86
+ "pytest>=9.1",
87
+ "pytest-asyncio>=1.4",
88
+ "pytest-cov>=7.1",
89
+ "pytest-timeout>=2.4",
90
+ "hypothesis>=6.168",
91
+ "httpx>=0.28",
92
+ "aiosqlite>=0.22",
93
+ "asyncpg>=0.31",
94
+ "opentelemetry-sdk>=1.28",
95
+ ]
96
+ lint = [
97
+ "ruff>=0.16",
98
+ "pyright>=1.1.414",
99
+ ]
100
+
101
+ [[dependency-groups.dev]]
102
+ include-group = "test"
103
+
104
+ [[dependency-groups.dev]]
105
+ include-group = "lint"
106
+
107
+ [tool.uv.sources.evalr]
108
+ workspace = true
109
+
110
+ [tool.uv.sources.lattice-core-ai]
111
+ workspace = true
112
+
113
+ [tool.uv.build-backend]
114
+ module-name = "artifactr"
115
+
116
+ [tool.deptry.package_module_name_map]
117
+ lattice-core-ai = "lattice_core"
118
+
119
+ [tool.pyright]
120
+ include = [
121
+ "src",
122
+ "tests",
123
+ "scripts",
124
+ ]
125
+ strict = [
126
+ "src",
127
+ "scripts",
128
+ ]
129
+ typeCheckingMode = "standard"
130
+ pythonVersion = "3.12"
131
+ venvPath = "../.."
132
+ venv = ".venv"
133
+
134
+ [tool.pytest.ini_options]
135
+ testpaths = ["tests"]
136
+ addopts = [
137
+ "--strict-markers",
138
+ "--strict-config",
139
+ "-ra",
140
+ ]
141
+ timeout = 60
142
+ timeout_method = "thread"
143
+ asyncio_mode = "auto"
144
+ asyncio_default_fixture_loop_scope = "function"
145
+ filterwarnings = [
146
+ "error",
147
+ "ignore:'asyncio.iscoroutinefunction' is deprecated:DeprecationWarning:backoff._decorator",
148
+ ]
149
+ markers = ["postgres: needs a PostgreSQL database (set ARTIFACTR_TEST_POSTGRES_URL)"]
150
+
151
+ [tool.coverage.run]
152
+ branch = true
153
+ source = ["src/artifactr"]
154
+ concurrency = [
155
+ "greenlet",
156
+ "thread",
157
+ ]
158
+
159
+ [tool.coverage.report]
160
+ fail_under = 100
161
+ show_missing = true
162
+ skip_covered = true
163
+ exclude_also = [
164
+ "if TYPE_CHECKING:",
165
+ "@overload",
166
+ 'assert_never\(',
167
+ "raise NotImplementedError",
168
+ '^\s*\.\.\.$',
169
+ 'class .*\(Protocol.*\):',
170
+ ]
171
+
172
+ [build-system]
173
+ requires = ["uv_build>=0.12,<0.13"]
174
+ build-backend = "uv_build"
@@ -0,0 +1,143 @@
1
+ [project]
2
+ name = "artifactr-ai"
3
+ version = "0.2.0"
4
+ description = "Build chat applications where people and agents collaborate through shared, versioned artifacts."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [{ name = "Alex Nodeland", email = "alex@ournature.studio" }]
9
+ requires-python = ">=3.12"
10
+ keywords = ["agents", "llm", "collaboration", "artifacts", "pydantic-ai", "mcp"]
11
+ classifiers = [
12
+ "Development Status :: 3 - Alpha",
13
+ "Framework :: Pydantic :: 2",
14
+ "Framework :: FastAPI",
15
+ "Intended Audience :: Developers",
16
+ "Operating System :: OS Independent",
17
+ "Programming Language :: Python :: 3 :: Only",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.13",
20
+ "Programming Language :: Python :: 3.14",
21
+ "Topic :: Software Development :: Libraries :: Application Frameworks",
22
+ "Typing :: Typed",
23
+ ]
24
+ dependencies = [
25
+ "lattice-core-ai>=0.1.0",
26
+ "pydantic>=2.13",
27
+ "jsonpatch>=1.33",
28
+ "jsonpointer>=1.9",
29
+ "pydantic-ai-slim>=2.51",
30
+ "opentelemetry-api>=1.28",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ sql = ["lattice-core-ai[sql]>=0.1.0", "sqlalchemy[asyncio]>=2.1", "alembic>=1.20"]
35
+ sqlite = ["artifactr-ai[sql]", "aiosqlite>=0.22"]
36
+ postgres = ["artifactr-ai[sql]", "asyncpg>=0.31"]
37
+ fastapi = ["fastapi>=0.141", "starlette>=0.46.0"]
38
+ mcp = ["mcp>=2.2", "starlette>=0.27"]
39
+ # The SDK's setup is lattice-core's; this package imports the SDK for its own views.
40
+ otel = ["lattice-core-ai[otel]>=0.1.0", "opentelemetry-sdk>=1.45"]
41
+ langfuse = [
42
+ "lattice-core-ai[langfuse]>=0.1.0",
43
+ "langfuse>=4.15,<5",
44
+ # The score mapping and ports that artifactr.scores' mirror builds on, and evalr.langfuse's
45
+ # score adapters, which send the mirror's scores to Langfuse.
46
+ "evalr[langfuse]>=0.1.0.dev0",
47
+ ]
48
+ litellm = ["lattice-core-ai[litellm]>=0.1.0"]
49
+ evals = ["evalr>=0.1.0.dev0"]
50
+
51
+ [project.urls]
52
+ Homepage = "https://lattice.alexnodeland.com/artifactr/"
53
+ Documentation = "https://lattice.alexnodeland.com/artifactr/"
54
+ Repository = "https://github.com/alexnodeland/lattice"
55
+ Issues = "https://github.com/alexnodeland/lattice/issues"
56
+ Changelog = "https://github.com/alexnodeland/lattice/blob/main/packages/artifactr/CHANGELOG.md"
57
+
58
+ [dependency-groups]
59
+ test = [
60
+ "pytest>=9.1",
61
+ "pytest-asyncio>=1.4",
62
+ "pytest-cov>=7.1",
63
+ "pytest-timeout>=2.4",
64
+ "hypothesis>=6.168",
65
+ "httpx>=0.28",
66
+ "aiosqlite>=0.22",
67
+ "asyncpg>=0.31",
68
+ "opentelemetry-sdk>=1.28",
69
+ ]
70
+ lint = ["ruff>=0.16", "pyright>=1.1.414"]
71
+ dev = [
72
+ { include-group = "test" },
73
+ { include-group = "lint" },
74
+ ]
75
+
76
+ # In lattice, evalr and lattice-core are the checkout's. A built wheel keeps the ranges in
77
+ # [project] alone.
78
+ [tool.uv.sources]
79
+ evalr = { workspace = true }
80
+ lattice-core-ai = { workspace = true }
81
+
82
+ [build-system]
83
+ requires = ["uv_build>=0.12,<0.13"]
84
+ build-backend = "uv_build"
85
+
86
+ # Distributed as artifactr-ai (ADR-0025); imported as artifactr.
87
+ [tool.uv.build-backend]
88
+ module-name = "artifactr"
89
+
90
+ # ─── dependencies ─────────────────────────────────────────────────────────────
91
+
92
+ # deptry can't tell from the workspace's editable install which module lattice-core-ai provides.
93
+ [tool.deptry.package_module_name_map]
94
+ lattice-core-ai = "lattice_core"
95
+
96
+ # ─── types ────────────────────────────────────────────────────────────────────
97
+
98
+ [tool.pyright]
99
+ include = ["src", "tests", "scripts"]
100
+ strict = ["src", "scripts"]
101
+ typeCheckingMode = "standard"
102
+ pythonVersion = "3.12"
103
+ venvPath = "../.."
104
+ venv = ".venv"
105
+
106
+ # ─── tests and coverage ───────────────────────────────────────────────────────
107
+
108
+ [tool.pytest.ini_options]
109
+ testpaths = ["tests"]
110
+ addopts = ["--strict-markers", "--strict-config", "-ra"]
111
+ # A test that runs for a minute is stuck: fail it, with every thread's stack.
112
+ timeout = 60
113
+ timeout_method = "thread"
114
+ asyncio_mode = "auto"
115
+ asyncio_default_fixture_loop_scope = "function"
116
+ filterwarnings = [
117
+ "error",
118
+ # backoff, which Langfuse uses, calls asyncio.iscoroutinefunction, deprecated in Python 3.14.
119
+ "ignore:'asyncio.iscoroutinefunction' is deprecated:DeprecationWarning:backoff._decorator",
120
+ ]
121
+ markers = [
122
+ "postgres: needs a PostgreSQL database (set ARTIFACTR_TEST_POSTGRES_URL)",
123
+ ]
124
+
125
+ [tool.coverage.run]
126
+ branch = true
127
+ # By path rather than package name, so the migrations Alembic loads from files are measured too.
128
+ source = ["src/artifactr"]
129
+ # SQLAlchemy runs run_sync callables in a greenlet, which coverage follows only when told.
130
+ concurrency = ["greenlet", "thread"]
131
+
132
+ [tool.coverage.report]
133
+ fail_under = 100
134
+ show_missing = true
135
+ skip_covered = true
136
+ exclude_also = [
137
+ "if TYPE_CHECKING:",
138
+ "@overload",
139
+ "assert_never\\(",
140
+ "raise NotImplementedError",
141
+ "^\\s*\\.\\.\\.$",
142
+ "class .*\\(Protocol.*\\):",
143
+ ]