Loom keeps your agents on track.

Tutorial 03: Set up project memory

This tutorial prepares one public test repository and its writable test store. Its single focus is the identity and scope foundation that every later coding-memory tutorial reuses.

Complete Tutorial 00: Connect your coding harness to Loom and Tutorial 02: Connect to multiple stores first.

Start here: make loom_demo the session default

YouLoad the configured store named loom_demo and make it the default for this session only. Confirm the effective session default, and do not edit ~/.loom/stores.toml.
AgentThe agent connects to the existing loom_demo store and changes only this session's default routing.
Resultloom_demo is now the default for this session. Persistent configuration was not changed.

1. Verify the tutorial repository and store

Continue in the public palmerpenguins repository that you checked out and opened as your coding harness's workspace in Tutorial 00. If that repository is not the current workspace, return to Tutorial 00: Connect your coding harness to Loom and complete its first step before continuing.

Repository scope and store selection solve different problems: the repository tells Loom which work the memory belongs to, while the store tells Loom where to read and write it. Verify both before creating facts so later sessions can retrieve them from the same boundary.

YouSummarize this repository and describe the effective session-default store before we make changes.
AgentThe agent inspects the repository and describes the effective store, confirming it is loom_demo. It reports the live store fields instead of assuming that recorded tutorial counts are still current.
ResultThis is a small Python package. Loader code is under palmerpenguins/, bundled CSV files are under palmerpenguins/data/, and tests are under tests/. [loom_demo] is loaded as the writable tutorial store; its description confirms the coding domain and that the store is reachable.

Before continuing, have the agent verify that the exact [loom_demo] alias exists and contains Project("loom_demo") plus the starter Roadmap and Decision used below. If the alias or seed is absent, reinstall the current Loom Cloud bundle and stop here. Do not use an unrelated [demo] or [tutorials] store just because its name looks close.

YouCreate a local branch named loom_demo for tutorial changes.
AgentThe agent creates or switches to the local loom_demo branch without changing a remote branch.
ResultThe local loom_demo branch is ready. Git will record source changes; [loom_demo] will hold durable agent memory about the work.

2. Resolve your GitUser identity

Loom attributes every fact to a real author so a shared store stays trustworthy across multiple developers. The author identity comes from git, not from you typing a name.

YouResolve my GitUser identity for Loom from this repository's git config.
AgentThe agent runs git config user.email and git config user.name, then checks whether a matching GitUser node already exists in [loom_demo].
Resultgit config user.email returned dev1@gmail.com (name: Developer One). This masked address is sample output. In a real run, every save and edge below substitutes the exact value returned by this repository's git config user.email. On a first run, [loom_demo] may have no matching GitUser node yet; on a rerun it may already exist.

Loom's substrate is resolve-or-reject: an edge can only point at a node that already exists, so the identity node must be created before any fact carries an Author() edge to it. Bootstrap it with a get-or-create save:

YouUsing the email you just read from git, make sure the matching GitUser exists in [loom_demo] so my facts can be attributed to it — create it if it is missing.
AgentThe agent saves GitUser("<actual git config user.email>") in [loom_demo], substituting the value it just read rather than the masked sample. If the node already exists, the save is an idempotent no-op — Loom returns the same fact id back — so this step never duplicates on a rerun.
ResultGitUser("dev1@gmail.com") exists in [loom_demo] (created just now, or already present from an earlier run — either way it is the same node). In your run, both this node and every Author() edge use the actual email returned by git.

Canonical deduplication does not merge new edges into an existing fact. If a tutorial fact already exists under another author, resaving its canonical returns the existing fact id and leaves its original Author() edge unchanged; report that history rather than claiming it was re-attributed.

If git config user.email is empty, points at a stale CI identity, or is otherwise not the identity you intend to use, fix it before saving anything — a wrong email silently forks a second author node that never matches your real history.

3. Build the repository, project, workspace, and organization scope chain

Facts are filed with InScope() edges to plain entity nodes, and those nodes are connected by PartOf edges into a chain: Repo -> Project -> Workspace -> Organization. Reads at a node automatically union up that chain — a repository-level read also sees project-, workspace-, and org-wide facts — so the chain has to actually exist, or reads from an ancestor come back empty even when the data is right there.

YouDerive the repository name from the configured Git remote. In [loom_demo], ensure Repo("palmerpenguins") exists with PartOf() -> Project("palmerpenguins"), and ensure that project continues through Workspace("palmerpenguins") to Organization("loom-tutorials"). Verify the complete chain. Then add only loom-scope: project=palmerpenguins workspace=palmerpenguins org=loom-tutorials to existing root agent guidance. If this repository has none of AGENTS.md, CLAUDE.md, or CODEX.md, create root AGENTS.md containing only that line.
AgentThe agent creates missing targets from the top down, then ensures the remote-derived Repo node and all three PartOf() hops. For an existing node with a missing hop it uses the idempotent edge-add operation; resaving the canonical would not merge the edge. It verifies Repo("palmerpenguins")Project("palmerpenguins")Workspace("palmerpenguins")Organization("loom-tutorials"). It preserves all existing guidance and adds only the one loom-scope line, creating root AGENTS.md only when no supported root guidance file exists.
ResultThe verified chain is Repo("palmerpenguins")Project("palmerpenguins")Workspace("palmerpenguins")Organization("loom-tutorials") (created or repaired on a first run; unchanged on a rerun). The supported root guidance now carries the same scope coordinates and no unrelated guidance changed.

The resulting Entity/Relation shape is:

Repo("palmerpenguins")
  PartOf → Project("palmerpenguins")
    PartOf → Workspace("palmerpenguins")
      PartOf → Organization("loom-tutorials")

Repo, Project, Workspace, and Organization are Entity types: they describe what each node is. PartOf is a Relation type: it connects those different entities without changing their identities.

The loom-scope guidance closes the loop between stored scope and live sessions. Without it, a new agent may infer the repository name but miss the workspace and organization, reducing hook reads to repository-only context and hiding project-scoped facts.

PartOf edges are one-directional and the union-up direction only goes toward ancestors. Reading from Project("palmerpenguins") now also picks up anything scoped at the workspace or organization level — but reading from Organization("loom-tutorials") directly will not show facts scoped narrowly at the project below it. Ancestors see down into nothing; descendants see up the whole chain. Keep that direction straight: file a fact at the narrowest node that applies, and read from wherever you're currently working.

4. Reveal what is already in the store

Before saving any project facts, ask for a bounded inventory of the whole store. This is more revealing than naming a few types in advance: it shows both the data you expected and anything you did not know was there, without letting a large type overwhelm the response.

The result should orient you, not act as a fixed snapshot. Counts grow as the tutorials add facts, while the concrete types and scope labels explain what each item means and where it applies.

YouIn [loom_demo], display up to 5 current items for every populated type in this Loom store.
AgentThe agent asks Loom for the store's current facts, groups them by their concrete entity type, and displays at most five items from each populated type. It also reports the total when a type has more than five items, so the cap is visible rather than silently dropping data.
Result
Decision (1):
  - small reviewed tutorial changes before live deploys

Directive (2):
  - ask before pushing origin main
  - keep access bundles local

GitUser (1):
  - dev1@gmail.com

Organization (1):
  - loom-tutorials

Project (2):
  - loom_demo
  - palmerpenguins

Repo (1):
  - palmerpenguins

Roadmap (1):
  - publish runnable onboarding demo for new agents [active]

Workspace (1):
  - palmerpenguins

Read this as "the inventory includes at least these" rather than an exact report: the counts and examples change as you work through this and later tutorials, and on a rerun you will also see the facts previous passes saved — extra Directives, a Finding, and PaidService entries from Tutorial 07, among others. That is expected, not pollution; this question is a quick store orientation, not a hard-coded report. On a fresh account it exposes exactly the seeded loom_demo starter facts alongside the identity and scope nodes you just created, in one bounded view. Only populated types appear; Tutorial 04 uses the type catalog to show declared types even when they have no facts yet.

This inventory is deliberately whole-store rather than project-scoped. The returned labels show that the seeded Directive, Decision, and Roadmap items are scoped to Project("loom_demo") — the demo store's own starter project — while the sample GitUser("dev1@gmail.com"), Repo("palmerpenguins"), and its project/workspace/organization ancestors are the identity and scope entities from §2–§3. Your real run substitutes its Git email. When you need only project-scoped coding facts, ask for that scope explicitly; identity and scope nodes do not themselves carry the same filing edge.

5. Takeaways