Workspace (0.9.39+)
A workspace is a shared notebook. Agents on a team write notes to it, read each other’s notes, and the framework handles the boring parts: slugs, YAML frontmatter, the auto-regenerated index, write atomicity, per-author attribution.
The metaphor matters. It’s a notebook, not a filesystem. Agents
call note(title, content) and never see a filename. They refer
to notes by title or by an opaque slug.
from loomflow import Agent, LocalDiskWorkspace
ws = LocalDiskWorkspace.temp()
researcher = Agent(
"Research the question. Write one findings note.",
model="gpt-4.1-mini",
workspace=ws.member("researcher", teammates=["writer"]),
)
writer = Agent(
"Read the researcher's notes. Write the final answer.",
model="gpt-4.1-mini",
workspace=ws.member("writer", teammates=["researcher"]),
)
await researcher.run("Is microservices worth it at 10 engineers?", user_id="team")
await writer.run("Write the recommendation.", user_id="team")The researcher and writer never see each other’s transcripts. The only thing that crosses between them is the curated note. That’s the point.
Why a notebook instead of shared history
When you run a team of agents, the naive approach is to pipe every agent’s full transcript into the next one. That blows up fast. Five specialists at 4K tokens each means the synthesizer reads 20K tokens of raw transcript, most of it reasoning noise.
The notebook fixes that. Each specialist writes one ~100-token note. The synthesizer reads five notes, not five transcripts. The context stays small and the signal stays high.
It also persists. Point a LocalDiskWorkspace at a fixed path and
a second run picks up where the first left off. The notebook
becomes a substrate the team gets smarter on, run over run. See
Workspace lifecycle for the v0.10
self-improvement surface.
The five tools an agent gets
Wire a workspace= and the framework installs five tools on the
agent’s tool host. It also augments the system prompt so the model
knows they exist.
| Tool | What it does |
|---|---|
note(title, content, kind) | Write a new note. The author is baked in; the agent never types it. |
read_note(slug_or_title) | Read one note by slug or partial title. |
list_notes(author, kind) | List notes, newest first. Returns summaries, not full bodies. |
search_notes(query) | Text-search the notebook. BM25 by default, semantic if an embedder is wired. |
update_note(slug, content) | Rewrite a note this author owns. The prior body is snapshotted to history. |
Note kinds are a small fixed set: finding, question,
decision, summary, artifact, plan, note. They’re
metadata, not partitions. list_notes(kind="decision") filters,
but every kind is visible to every teammate.
Wiring it on an Agent
The workspace= kwarg accepts several shapes.
A WorkspaceMembership via ws.member(...). This is the usual
path. One call carries the notebook, this agent’s author identity,
and the team it’s collaborating with:
agent = Agent(
"...",
workspace=ws.member("researcher", teammates=["analyst", "writer"]),
)A bare Workspace instance. Share the notebook with no
specific identity. Notes get attributed to the generic agent:
agent = Agent("...", workspace=ws)A string. The resolver builds the backend for you:
agent = Agent("...", workspace="temp") # fresh temp dir
agent = Agent("...", workspace="memory") # InMemoryWorkspace
agent = Agent("...", workspace="./team-notebook") # LocalDiskWorkspace at a pathA dict. Declarative, TOML-friendly. Identity keys (author,
teammates) are optional:
agent = Agent(
"...",
workspace={
"backend": ws,
"author": "researcher",
"teammates": ["analyst", "writer"],
},
)The same workspace= kwarg works on Workflow and Team. Pass it
once at the workflow level and nested agent steps inherit it
through the ambient context, the same way memory= propagates.
Two backends
| Backend | Storage | Use |
|---|---|---|
LocalDiskWorkspace | One markdown file per note, plus an auto-regenerated WORKSPACE.md index | Production. Survives restarts. cat it during a run. |
InMemoryWorkspace | Python dicts | Tests, notebooks, ephemeral coordination inside one process |
LocalDiskWorkspace has three constructors:
from loomflow import LocalDiskWorkspace
# Fresh temp directory. cleanup=True (default) wipes it on aclose().
ws = LocalDiskWorkspace.temp(prefix="loom-research-", cleanup=False)
# Open or create at a fixed path. Never auto-cleans.
ws = LocalDiskWorkspace.open("./team-notebook")
# Direct constructor — same thing, with seed_paths for reference docs.
ws = LocalDiskWorkspace("./team-notebook", seed_paths=["./brief.md"])On disk you get a real directory you can inspect:
team-notebook/
research_team/
WORKSPACE.md # auto-regenerated index
notes/
001-rate-limits.md
002-data-consistency.mdWORKSPACE.md regenerates atomically on every write, so a
concurrent reader always sees a consistent table of contents.
Multi-tenancy
Every workspace method takes an optional user_id. Notes from
different user_id runs never appear in each other’s listings,
even on a shared workspace root. The disk backend partitions by
user_id at the directory level. Same partition guarantee you get
everywhere else in the framework.
Read more
Workspace vs Memory. Memory is per-agent conversational state: episodes and facts, scoped to one agent’s runs. The workspace is cross-agent shared state: notes one agent writes that another agent reads. Different scope, different lifecycle. A team usually wants both.