Workspaces
Knowl workspaces provide linked federation across related repositories without merging their databases.
# Create a machine-local workspace.
knowl workspace init product
# Run inside each repository that should join it.
knowl workspace add product
knowl workspace status
# Bare: pick from a list, with the categories worth sharing already ticked.
knowl workspace promote
# Or name them, which skips the picker and dry-runs until --apply.
knowl workspace promote --category decision
knowl workspace promote --category decision --apply
A repository joining a linked workspace records defaultVisibility: workspace, so the knowledge
it writes from then on is readable by its peers. The command prints that a default decided it and
how to decline; --default-visibility repo opts out, and knowl workspace set with no flags
prints the current value. Repositories already listed in a manifest are never moved: an absent
defaultVisibility still resolves to repo, because changing what omission means would publish
every linked repository's next write on account of an upgrade rather than a decision, and there is
no demote. Knowledge written before the default, or under --default-visibility repo, stays
private until it is promoted.
For another checkout or machine, copy the workspace manifest and join from each repository:
knowl workspace join /path/to/workspace.json --name api
The shipped workspace commands are:
| Command | Purpose |
|---|---|
knowl workspace init <name> |
Create a workspace outside its member repositories |
knowl workspace add <name> [--name <repo-name>] [--default-visibility <repo|workspace>] [--promote-existing] [--force] |
Link the current repository; shares its new writes by default in a linked workspace |
knowl workspace join <manifest> [--name <repo-name>] [--force] |
Adopt a copied manifest and map this checkout |
knowl workspace list |
List workspaces known to this machine |
knowl workspace status [--verbose] |
Show this repository's membership and peer health |
knowl workspace remove <repo-name> [--export-first] |
Unlink the current repository, retiring its name if it still owns atoms |
knowl workspace promote [--category <list> | --id <id...>] [--apply] |
Share locally owned atoms with linked repos. Bare opens a picker with decision, constraint, architecture, goal, skill preticked and confirming applies; --apply is only needed on the flag path |
knowl workspace repin-embedding [--yes] |
Move the workspace to this repository's embedding model and list the peers that must reindex |
Federation and ownership
The external manifest contains machine-local checkout paths. Membership is two-sided: the
manifest names the repository, and that repository's configuration points back to the workspace.
Every member continues to own a separate .knowl/knowl.db.
Normal workspace add refuses to link when .knowl/config.json is tracked by Git.
--force bypasses only that tracked-config guard; it does not repair embedding-identity
mismatches or item ownership.
Only an explicit current query fans out to available peers. A promoted peer result is labeled
with its repo and is read-only from the querying repository — unless the call names that repo,
which runs it as that repo rather than reaching across from this one; see
Doing a linked repo's work from here. A shared peer atom
can also be opened whole by id, without acting as anything: see
Reading a linked repo's atom by id. Mutations, historical
asOf queries, recent context, context packs, work loops, synthesis, code indexing, and implicit
lifecycle context remain local. Missing, unreadable, or schema-incompatible peers are skipped and
disclosed in the response rather than causing healthy local retrieval to fail.
One ranker, pointed at each repo. Retrieval takes an explicit database handle, so a linked
repository is searched by the same code that searches the local one: the same FTS and vector
selection, and the same recency, confidence, freshness, category and exact-identifier scoring.
Candidates are selected per repository, then scored in a single pass over all of them together
— recency is normalized against the candidate set it is given, so ranking each repository
separately and combining the results would make every repository's newest atom equally recent.
Identical content held by two repositories is deduplicated before the result cap, keeping the local
copy, so a shared fact cannot consume two slots and return a short list. A peer handle is opened
query_only, and the visibility predicate is applied inside the SQL, so a peer's repo-private
row is never read into the querying process at all.
Cross-repository overlap is reported on write. A knowledge write inside a workspace also checks
the linked repositories, and reports an exclusive conflict key or a same-subject atom held
elsewhere, naming the owning repository. Both the single-atom and batch writers do this, the batch
per atom. It is advisory and never mutates: that atom belongs to another repository, knowl_update
refuses foreign ids, and only its owner can retire it. Bounded to a few candidates per peer, and
non-fatal — an unreadable peer yields no report rather than a failed write. Outside a workspace it
costs one check.
Promotion is preview-first and accepts active, private, locally owned atoms selected by category
or ID. Both workspace add and workspace join enforce a compatible embedding identity, reject a
nested checkout, and refuse a Git-tracked .knowl/config.json without --force. Nothing reaches
across from one repository into another: a foreign id is refused, and the only way to change a
linked repository's knowledge is to run the call as that repository, which makes the atom local
and the guard satisfied rather than bypassed. There is no demote/unshare command, and no
workspace-wide historical view.
workspace remove --export-first is an acknowledgement that the repository still owns
knowledge; it does not create an export. A removed name is retired only when the repository
still owns active atoms, since the name is the ownership key on everything it wrote. A repository
that owned nothing releases its name for anyone; a repository that owned atoms keeps exclusive
claim on the name and reclaims it by re-linking, while every other repository is refused.
Reading a linked repo's atom by id
A federated query returns rows from every linked repository, each labeled with its owner. Asking
for one of those rows whole — knowl_query { id } — resolves it too, so reading a federated
result no longer means switching repositories.
The record crosses; the checkout-relative fields do not. A foreign atom arrives with its content,
reasoning and alternatives, and without affectedPaths or evidence, because those name files
in the owning repository's checkout and resolve against its database and working tree — answered
here they would be measured against the wrong tree. It carries a foreign block naming the owner,
so an absent affectedPaths reads as deliberate rather than as an atom that cites nothing, and so
the agent knows where the item can be changed.
It reaches exactly the rows a query reaches, and no others. A repository's private knowledge
stays private until workspace promote shares it; knowing an id is not a way around that, since
ids are not secret — they travel in supersession chains and conflict reports. A private row is
reported as not found rather than as a refusal, because "that one is private" would confirm it
exists.
Reading is all this grants. Updating, superseding or retiring another repository's item is refused by the same guard as before, with the same message. A miss says the linked repositories were searched too, and says "readable from here": a repository that is not checked out on this machine was never asked, and must not be reported as one that answered no.
Ownership
origin_repo is stamped when an atom is created, so every write made while linked is owned and
promotable. Joining additionally backfills the rows that already existed, which are by definition
the joining repository's own.
written_by records the repository whose session authored an atom, when that is not the one that
owns it — the case that arises from naming a repo on a call. Ownership is unaffected: the atom is
the target's, and the target promotes and retires it. NULL means the owner wrote it, which is
the ordinary case, so the column is set only when author and owner genuinely differ; stamping a
repository's own name on its own atoms would make the field say nothing on almost every row. Rows
predating the column are NULL for the same reason rather than as a gap — they were written
before a repository could act as another, so their owner is exactly who wrote them. It is not part
of the lifecycle fingerprint: authorship is fixed at write time and never diverges, so including
it would make every cross-repository atom look changed to a peer holding the same one.