Context layer
Guides are the half of Mossbear your agent is graded against. The context layer is the other half: the notes, references and other files you want an agent to have, held in one account and delivered into whichever repo you are standing in.
Bundles
Section titled “Bundles”A bundle groups context by project or topic. It has an id and a url-safe slug, and the
slug is what pull_bundle, create_artifact, file_artifact and mossbear pull <bundle>
all address.
An artifact in a bundle is one of three types: guide, content, or asset. Two of those
are rows in one table; a guide is a row in a different one, which is why it behaves
differently everywhere below.
Membership is the only thing that ties an artifact to a bundle, and one artifact can belong to
several: an update through update_artifact propagates to every bundle the artifact is in.
Link results for review
Section titled “Link results for review”MCP create_artifact and update_artifact return artifact.url after creating or updating an artifact.
pull_bundle, list_inbox, and pull_inbox return url on each artifact.
When using writeToDir, the reply pairs successful paths with review URLs in links: each entry carries
itemId, path, and url. Skipped files have no written-path link.
An agent can save the URL from a pull and include it in chat after editing the
local file; the URL opens the canonical artifact, so it reflects edits once they sync.
The import tool returns guideId and url in each per-file result when the
server supplies a canonical guide identity. Older servers may omit that identity;
the agent should not guess a link from a file path. A conflict result links the
existing guide for inspection and still reports that the edit was refused.
URLs use the configured dashboard address, including a local or staging dashboard. Artifact URLs open their authenticated pages; inbox URLs open the note in Library’s Inbox view. Signed-out readers are directed through login. These links do not change sharing permissions.
Comments on an artifact
Section titled “Comments on an artifact”From the dashboard, a person can leave a comment on a bundle artifact’s page — a request about the artifact, never a change to it. (A guide picked up before it had a body of its own is the one exception with nowhere to write one; every guide authored since does.)
Comments are announced, and read on demand. Before your first turn,
mossbear refresh says how many are waiting and names the command that
reads them; the bodies come from mossbear comments, the list_comments tool,
or pull_bundle, which returns a comments field on the artifact a comment is
about. Each body arrives wrapped in the same untrusted markers as the rest
of what a person wrote, because it is a request from someone in your
workspace, not an instruction from Mossbear.
Findings Mossbear raised are listed too, marked as drafts. When
Mossbear raises a finding about one of your guides (what goes wrong, with the
evidence behind it, and no replacement text), list_comments and
mossbear comments list it to your own agent before you have looked at it,
and name it as a draft. Your agent shows it to you and changes nothing until
you say so. Press Accept on the finding in the dashboard and it becomes
a request like any other comment: it is announced at session start and rides
pull_bundle. Your agent then decides what to change. Teammates never see a finding raised from your sessions.
The announcement is capped; the read never is. A comment that is still
open stops being announced in a repo once you have been told about it a few
times there, so silence before your first turn means “nothing new to
interrupt you about” and never “there are no comments”. mossbear comments
returns every open comment however often it is asked. The cap is kept per
reader and per repo, which is what keeps one person’s silence from becoming
everyone’s, and why it survives a fresh clone.
There is no reply tool and no thread. An agent answers a comment the way it
answers anything else about an artifact: by editing it through update_artifact. A
comment stays open until a person marks it done from the artifact’s page in the
dashboard — reading it never counts as that.
The capture inbox
Section titled “The capture inbox”The inbox is a query, not a container: a captured note that you have not archived and
have not filed into any bundle, newest first. Filing it into a bundle is what takes it
out. No bundle is special here, so a bundle named inbox has nothing to do with it.
That distinction has a consequence worth knowing before you write an allowlist. See
--bundles below.
mossbear pull
Section titled “mossbear pull”mossbear pull <bundle> does two things. It writes the bundle’s artifacts into
.mossbear/context/<bundle>/, and it records a subscription in the repo’s
.mossbear/config.json. The subscription is what makes the pull durable: mossbear refresh
reads it on every run and keeps those bundles fresh, reconciling deletions so a note you
delete in the dashboard stops reaching agents through this repo.
Run it with no bundle in an interactive terminal and you get a picker. With no terminal and no bundle it refuses rather than guessing.
What to commit
Section titled “What to commit”Commit .mossbear/config.json, .mossbear/.gitignore, .mossbear/.gitattributes,
.mossbear/evals/ and .mossbear/placement.jsonl, whichever of them exist. The placement
file records where each delivered file lives, so a teammate’s clone knows which files a
bundle manages. .mossbear/context/ stays out of git, and so does the placement file inside
it, which records where that folder’s artifacts landed. A refresh that delivers a new
artifact into .mossbear/context/ leaves git status clean.
If your repo has a .mossbear/docs-placement.jsonl from an earlier CLI, the next sync
renames it to placement.jsonl and moves any .mossbear/context/ lines into the context
folder’s own file. Commit that change once, and upgrade everyone who works in the repo:
an older CLI doesn’t know the new name.
Two places a subscription can live
Section titled “Two places a subscription can live”mossbear pull <bundle> records the subscription in this repo’s committed
.mossbear/config.json, so everyone who clones the repo receives the bundle. --account
records it against your account instead: nothing is committed, and every checkout you sync
receives the bundle, on this machine and the next one. A bundle can be named by both, and
that is one subscription in each place rather than a conflict.
Where a subscription’s artifacts land
Section titled “Where a subscription’s artifacts land”By default each artifact lands where its own scope says: a project guide in the checkout, a
guide you scoped to your home directory in your machine-wide guidance. --deliver overrides
that for everything the subscription carries.
--deliver repolands the bundle’s artifacts at their paths in this checkout.--deliver globallands them in your machine-global skills, so they reach every repo you work in without appearing in any of them.
That is what lets a repo name a bundle to load into your global skills, and an organisation distribute one bundle of standard skills that each member receives wherever they work.
Omit --deliver and the subscription keeps whatever target it already had; a bundle that has never
named one routes each artifact on its own scope, which is what an existing subscription — including your
own my-guides — already does.
The repo’s target is the deliver key beside the bundle id in .mossbear/config.json, and
your account’s is on your own subscription. They are independent: if the repo asks for
repo and your account asks for global for the same bundle, you get both. Delivery into
the checkout still follows the ordinary rule that git is the source of truth for everything
outside .mossbear/ — an unattended mossbear refresh reports those files rather than writing them.
A bundle published from one repository
Section titled “A bundle published from one repository”mossbear pull <bundle> --home claims this repo as the bundle’s home. From then
on the bundle’s artifacts are published from this repo’s published branch (main
unless you name another) and nowhere else: a change is a pull request here,
reviewed here, and publishes once it merges. Everywhere else —
a teammate’s mossbear complete push-back, an edit in the dashboard — the artifacts are
read-only, and the attempt is refused by name rather than quietly dropped. Your
local copy is never overwritten and never deleted; it is left exactly as it is
and named in the report.
Delivery is untouched. Everyone who subscribes still receives the bundle on every sync; what a home takes away is the right to publish back.
That is what an organisation needs to distribute a set of standard skills: one repository is their source of truth, review happens there, and every member receives them wherever they work.
mossbear pull <bundle> --home again from a different repo moves the claim, and
any member of the bundle’s workspace can move it — the claim is a property of the
bundle, so a mistyped id is never unrecoverable.
Publishing from the home repository
Section titled “Publishing from the home repository”Publication is how a merged change reaches everyone subscribed to the bundle this repo is the home of:
edit tracked skill or guide → commit/review → merge to main → subscribers refreshIt publishes only what landed on the published branch — main unless the
block names another with published.branch. Bytes come from that branch, never
from your working copy or the branch you have checked out, so what a subscriber
receives is something a reviewer approved in this repository. Work on a feature
branch is not published.
What this repo publishes is declared in .mossbear/config.json, beside the
subscription and the project id:
{ "published": { "paths": ["skills/**", "docs/guides/*.md"] }}The declaration that counts is the one on the published branch, so a change to it publishes once it is merged there too.
mossbear publish fetches the published branch and publishes the declared set
from it, with a preview first. --no-fetch publishes what your clone already
has. mossbear publish <path> adds a path to published.paths; it publishes
once that change is merged.
A repo can be the home of more than one bundle — plans in one, backlogs in
another. Claim each with mossbear pull <bundle> --home, then route paths to a
bundle by its slug under published.bundles:
{ "published": { "paths": ["docs/plans/*-plan.md"], "bundles": { "backlogs": ["docs/plans/*-backlog.md"] } }}published.paths publishes to the one home bundle no entry under
published.bundles names; if several are unnamed, publishing stops and says
which to name. A path goes to every bundle whose declaration matches it, so a
file named in two declarations is published to both and kept current in each.
Narrowing a declaration withdraws what it stopped covering. When a path you
narrowed or removed is no longer declared for a bundle, publication withdraws
that bundle’s copy, and the file stays in your repo. A change to
.mossbear/config.json merged on the published branch withdraws what it stopped
declaring. Any other undeclared copy waits for mossbear publish, which lists
it as “no longer declared” before asking.
To empty a routed bundle, set its entry to [] ("backlogs": []); don’t delete
it. Deleting an entry stops publishing to that bundle but withdraws nothing,
because without the entry Mossbear cannot tell which bundle published.paths
still covers. Emptying or deleting published.paths withdraws nothing for the
same reason; to empty that bundle, name it under published.bundles with [].
Only copies this repo published are withdrawn. A copy that is also filed in
another bundle is kept and named, since withdrawing it would take it from that
bundle too. A document someone created in the dashboard is never touched.
Deleting a published file
Section titled “Deleting a published file”Delete the file and merge the deletion; the next publication withdraws the artifact. Subscribers stop receiving it, and their copy goes on the next refresh — unless they have edited it, in which case the file stays and Mossbear says it is no longer tracking it. Leaving the file in place would let guidance you deliberately deleted keep reaching agents in every repo that subscribes.
The artifact itself is not deleted. It stays in your dashboard, marked removed, with its history and the commit that removed it — so “where did this skill go?” has an answer. Merge a file at that path again and the next publication brings it back as a new revision of the same artifact.
Renaming a file is a deletion plus a publication, and Mossbear does not try to spot the pair: subscribers see the old path withdrawn and the new one arrive.
When publication happens
Section titled “When publication happens”Every agent session hook checks whether the published branch has moved since that clone last published, and publishes what it changed. It reads only what your clone already has — the hook never fetches, so it never uses your git credentials and never deepens a clone. In practice that means:
- After
git pullbrings in a merge, the next hook publishes it. - After
git pushfrom your clone, the next hook publishes it: pushing moves your clone’s own copy of the branch. - A fresh clone, such as a cloud agent session, publishes straight away.
- A merge on GitHub that no clone has pulled publishes nothing until one
does. Run
mossbear publishto publish it on demand.
The branch is read from your remote — origin/main, not your local main — so
an unpushed commit is never published: a later git pull --rebase could still
rewrite it. A fork that publishes its upstream’s branch names that remote with
published.remote ("upstream"), and a repo that publishes another branch
names it with published.branch. Both are read from main, never from the
branch you are on, so a branch cannot point publication at itself; the branch
they name must declare the same two values. A clone that lacks the named remote
publishes nothing. In a repository with no remotes, the local branch
publishes directly.
A publication made this way has no preview and asks no question. The consent for
it is the published block itself — opt-in, scoped to globs rather than to the
repository, reviewable in a diff, and merged before any of it runs.
mossbear publish typed by hand keeps its preview.
On a team, whichever member’s clone notices a merge first publishes it, under their own token, and each published revision records the commit it came from. Publishing from the merged branch is a convention the CLI follows, not a check the dashboard makes: any member of the bundle’s workspace can still publish to it.
If the published branch is rewritten (a force-push) after a revision was
published from it, Mossbear does not publish over that revision; it says so,
and a person decides which history is right. A session hook that finds a copy
published from a commit its clone does not have says so too, since it cannot
tell a newer commit from a discarded one without fetching; mossbear publish
fetches the branch and settles it.
Switching one artifact off for yourself
Section titled “Switching one artifact off for yourself”An account subscription can mute individual guides. A muted guide stops being delivered to you — in the checkout and in your machine-wide skills — while everyone else on the same bundle keeps receiving it. It is your own switch, not the shared one: a repo subscription lives in a committed file that everyone who clones the repo reads, so it has no mute at all.
A muted skill leaves your machine-wide skills one repository at a time, as each of your checkouts syncs. Mossbear only removes a file once no repository still asks for it, and a sync in one repository speaks only for that one.
Exporting instead of subscribing
Section titled “Exporting instead of subscribing”--once exports the bundle into its folder and records no subscription. Nothing keeps it
fresh afterwards. --out <dir> writes to a directory you choose and implies --once,
because sync only refreshes the default location and a subscription pointing somewhere
else would be a promise the sync does not keep. --out needs an explicit bundle: it
targets one directory, so it is refused with the multi-select picker rather than quietly
ignored.
--types, --query and --limit narrow an export. They apply to exports only, so they
need --once or --out.
mossbear mcp
Section titled “mossbear mcp”mossbear mcpThis runs an MCP server over stdio that exposes your bundles and inbox to any agent that speaks the protocol. It needs a token. Point your agent’s MCP client at it the way you would any other stdio server. Human-facing messages go to stderr, because stdout is the JSON-RPC channel.
The tools it registers:
| Tool | What it does |
|---|---|
list_bundles | List the bundles this server can reach, with id, slug and name. |
create_bundle | Create a bundle from a human-readable name. The slug is derived unless you pass one. |
pull_bundle | Pull artifacts from a bundle. Filter by types, a query substring, and limit (100 by default, 500 at most). |
read_artifact | Read one artifact by URL/id, a saved version, or optional checkpoint history (includeHistory). |
create_artifact | Create an artifact from a name and content, optionally filed into a bundle. |
update_artifact | Replace an artifact’s content, against the expectedVersion you read. |
file_artifact | Add an existing artifact to bundles, or remove it from them. |
list_inbox | List unarchived inbox notes, newest first. |
pull_inbox | The same, and it can write the notes to disk instead of returning their bodies. |
archive_inbox | Archive inbox notes by id. |
list_comments | Every open comment people have left about the artifacts this repo receives. |
comment_evidence | Why Mossbear raised a finding list_comments handed you: its claim, the change it predicts if the guide is fixed, and the evidence for it: the sessions or turns it cites, a skill’s load counts, or the authoring check it failed. |
import | Reaches the rules-import endpoint rather than the context client. |
artifact_feedback | The open comments on one artifact, or on up to 25 at once with artifactIds, and, for a guide, what grading found about it, including your newest failure-pattern analysis of it. |
pull_bundle and pull_inbox take a writeToDir that puts artifacts on disk and returns
the paths instead of the bodies. It must be inside the current working directory, and it
must not be an agent-guidance directory (.claude/, .agents/, .cursor/), because
files written there are re-imported as new guides. A file already at a destination path
with different content is left alone and reported in skipped.
Updating an artifact you did not just read
Section titled “Updating an artifact you did not just read”update_artifact requires expectedVersion, the version you last read
for that artifact, which read_artifact or pull_bundle returns. Without it the write would go over whatever
the artifact says now, including an edit someone made since you read it. When the artifact has
moved on, the update is refused. Read it again, reconcile your edit with the current
body, and update with the version returned by that read.
An update replaces content and nothing else: it takes no name, bundle or format, and
the edit propagates to every bundle the artifact belongs to.
Citing where an artifact came from
Section titled “Citing where an artifact came from”When you create an artifact out of other artifacts, cite them in sources, each as
{artifactId, version?, contentHash?}. They are recorded on the first saved revision as
the writer’s assertion, and a citation that pins both a version and a hash the server can
match is recorded as verified. update_artifact takes sources too, but they describe only
the revision that write creates: they are not inherited from the previous one, so omit them
to record none.
Read or update a linked artifact
Section titled “Read or update a linked artifact”Use Mossbear’s MCP tools or CLI when a user gives you an artifact link. The link identifies the artifact; browser interaction requires a separate request.
read_artifact({ artifactId: "<URL-or-id>" }) returns the current content and version.
Pass version to read one saved snapshot, or includeHistory: true for checkpoint
metadata with ids, hashes, authors, timestamps and nextCursor. Pass that cursor
as before to read an older page. Snapshot content can be unavailable; that state
is explicit. Current versions track concurrent edits, while history retains
checkpoints, so saved numbers can have gaps.
update_artifact updates the same artifact with artifactId, content and
expectedVersion. Under a bundle allowlist, direct reads and updates are refused, so use
pull_bundle for reads. A scope refusal does not authorize switching to CLI or broadening
the grant. Creating under an allowlist requires a permitted bundle; create_artifact without
a bundle saves an unfiled artifact, which appears in the Library. A rejected update is not
retried with a newer version automatically; read again and reconcile the changes first.
The terminal equivalents share the validated API client:
mossbear artifacts read '<URL-or-id>' --jsonmossbear artifacts history '<URL-or-id>' --jsonmossbear artifacts read '<URL-or-id>' --version 1 --jsonmossbear artifacts update '<URL-or-id>' --file revised.md --expected-version 3Use the current version returned by a read for the update, rather than copying an example number or guessing from saved history. URLs must belong to the configured dashboard. If a needed operation is unavailable, report that gap.
Guides do not ride create_artifact
Section titled “Guides do not ride create_artifact”You cannot create a guide through create_artifact. Its format accepts only the
creatable content formats, and guide is deliberately absent. Creating one would write a standalone
row with no guide registration behind it, so the caller would get a success for
something that is not a guide.
Updating an existing guide is different: that is addressed by its id alone and needs
nothing from that list. Guides get into your account through mossbear link, and into a
repo through the bundles they belong to.
Two more paths deliberately leave the context client. import reaches the rules-import
endpoint, because a guide cannot ride create_artifact; list_comments reaches the
comments endpoint, because it answers “what has been left for me” rather than “what is in
this bundle” and so has no bundle to pull from.
The allowlist gates list_comments and comment_evidence. A note is about an artifact a
bundle delivers, so a grant limiting which bundles are reachable limits which comments
are. artifact_feedback is a direct artifact read, so like read_artifact it is refused
under any allowlist.
Restricting what the server can reach
Section titled “Restricting what the server can reach”mossbear mcp --bundles work,design --read-only--read-only refuses every context-layer write — create, update, file, archive — regardless of
what your config says.
--bundles is a comma-separated allowlist of bundle slugs. Unset, it falls back to
mcp.bundles in your config, and then to every bundle. A grant matches a bundle by
either of its addresses, its slug or its id, so an allowlist written one way does not
refuse a tool call that addresses it the other.
The inbox is granted separately, by the token @inbox:
mossbear mcp --bundles work,@inboxA slug is lowercase alphanumerics and hyphens starting with an alphanumeric, so @inbox
can never name a bundle. That is the point. The inbox gate used to be spelled as the
plain slug inbox, and inbox is an ordinary creatable slug, so a user who named a
project bundle inbox and allowlisted it would have silently granted read and archive
over every note on the account.
A server holding @inbox reads the inbox and nothing else. Bundles are reached only
through a bundle grant.
Where the bytes land
Section titled “Where the bytes land”One function writes every context byte to disk, which is what keeps a pull, a
writeToDir and the sync reconciler from disagreeing about what landing an artifact at a
path means. Before it, four writers held their own copies of those rules and the copies
disagreed: a writeToDir that clobbered the repo’s own files, a pull that resolved a
placement conflict by deleting it, and a cache record that discarded half a baseline.