Share charm context with teammates

Three opt-in toggles share memory, decisions, and attribution with teammates via the same git repo, no server required.

What team-sync gives you

Cantrip is a single-operator tool by default — every memory and every decision lives only on the machine that recorded it. Teams of two to five charm authors collaborating on the same repo hit the same gap repeatedly: each operator builds up context the others never see.

Team-sync closes that gap with three opt-in toggles. All three are file-based, all three live alongside the charm in git, and all three are reversible by removing the <charm>/.cantrip-shared/ directory or flipping the matching setting back off:

The three are independent — you can flip any one on without the others. None of them needs a Cantrip server, and none of them changes behaviour for an operator who has not opted in.

Share memories across the team

The memory subsystem already has two scopes (charm and global — see Use durable memory). Team-sync adds a third surface: an opt-in shared directory under the charm root that everybody on the team commits to git.

Turn it on

Set the environment variable before launching Cantrip:

CANTRIP_TEAM_MEMORY_WRITES=shared cantrip run .

The setting takes one of three values:

What teammates see

Reads always merge the shared directory regardless of the write setting, so an operator who flipped to shared last week still sees teammates' memories today even after toggling back to local. Shared entries surface in /memory listings with the source: shared marker so they're easy to tell apart from local-only entries.

The on-disk format is the same Markdown-with-frontmatter shape as global memory:

---
title: postgres-version
kind: fact
source: shared
created: 2026-04-30T12:00:00
status: active
tags: []
citations: []
---

Workload pins Postgres 16.

source: shared is the marker that makes a memory team-visible. Hand-written files in .cantrip-shared/memory/ work just as well as auto-writer captures — the directory is a regular folder, not a database.

When to use shared scope

Three rules of thumb:

Share the decisions log

Cantrip records every routing decision (which model, which substrate, which path) in a per-session SQLite table. Team-sync adds an append-only JSONL log alongside it so teammates pick up the why on the next pull.

Turn it on

CANTRIP_TEAM_DECISIONS_WRITES=shared cantrip run .

Two values:

What's in the log

One JSON object per line:

{"type": "substrate", "choice": "K8s", "reason": "containers", "timestamp": "2026-04-30T12:00:00"}
{"type": "framework", "choice": "ops", "reason": "team standardised", "timestamp": "2026-04-30T12:01:34"}

On load, every entry is flagged with source="shared" so:

Reads merge regardless of the write setting

Just like shared memory, reads always merge the shared decisions log when charm_path is set. Toggling CANTRIP_TEAM_DECISIONS_WRITES only changes whether new decisions get pushed to the file; the rest of the team's history is always visible.

Surface which human steered each commit

When auto-commit-per-turn is on, Cantrip stamps every commit with a Co-Authored-By: Cantrip <noreply@aotearoa.dev> trailer to mark the agent as a co-author. Team-sync extends this with a second trailer built from your local git config:

feat(charm): add ops-tracing integration

Prompt: Add ops-tracing to src/charm.py …

Touched:
  - src/charm.py

Co-Authored-By: Cantrip <noreply@aotearoa.dev>
Co-Authored-By: Tony Meyer <tony.meyer@example.com>

No setting required — the trailer is added automatically when git config user.name and user.email are set. Skipped silently for single-operator setups where the git config matches Cantrip's canonical, so no behavioural change for existing solo installs.

Where the files live

<charm>/
├── .cantrip                      ← per-operator SQLite session (gitignored)
├── .cantrip-shared/              ← committed; teammates pull this
│   ├── memory/
│   │   ├── postgres-version.md
│   │   ├── ops-tracing-required.md
│   │   └── …
│   └── decisions.jsonl
└── src/
    └── …

A few things worth noting:

Conflict policy — textual git merge

When two teammates edit the same shared memory or append the same decision concurrently, git resolves the merge textually:

If the JSONL file accumulates unwanted history, hand-edit it. Cantrip never rewrites the file outside the append path, so removing a stale line is safe.

Worked example — two operators

Alice picks up a new charm and turns on team-sync from the shell:

export CANTRIP_TEAM_MEMORY_WRITES=shared
export CANTRIP_TEAM_DECISIONS_WRITES=shared
cantrip run .

She does some work. The auto-writer captures a memory ("postgres-version") and Cantrip records a routing decision ("framework=ops"). Both land under .cantrip-shared/. Auto-commit-per-turn picks them up and pushes a commit with both trailer lines. She pushes:

git push

Bob pulls. He doesn't even need the env vars set — reads always merge the shared layer:

git pull
cantrip run .

His next session starts with Alice's postgres-version memory loaded into the prompt index and her framework=ops decision visible in /decisions. The shared memory shows up in /memory with a source: shared marker so he knows it came from a teammate.

When he wants to write a shared memory of his own, he flips the env var on for that session:

CANTRIP_TEAM_MEMORY_WRITES=shared cantrip run .

His new memory lands in .cantrip-shared/memory/, auto-commit picks it up, and Alice gets it on her next git pull.

What team-sync is not

Troubleshooting

"Why don't I see my teammate's memories?"

Check that .cantrip-shared/ is actually in the repo:

git ls-files .cantrip-shared/

If the directory is missing, your teammate hasn't pushed any shared writes yet, or .cantrip-shared/ is in .gitignore somewhere up the tree. Cantrip never auto-creates the directory — it's only made on the first shared write.

"I want to stop sharing without losing what's already shared"

Just unset the env vars:

unset CANTRIP_TEAM_MEMORY_WRITES
unset CANTRIP_TEAM_DECISIONS_WRITES

Existing shared files stay where they are — teammates keep seeing them on git pull until somebody removes the files explicitly. New writes go back to the local SQLite store.

"I want to wipe the shared layer entirely"

git rm -r .cantrip-shared/
git commit -m "Stop using cantrip team-sync"

Reverting to a single-operator setup is just removing the directory. Local SQLite stores are untouched.