Universal safe-save for concurrent text files — let a user and one or more AI agents edit the same file together without lost updates.
Project description
cotype — CLI reference
Universal safe-save for concurrent text files. The Python implementation that gets installed by editor integrations and agent harnesses.
For the user-facing pitch, the demo, and the quick start, see the monorepo top-level README. This document is the technical reference: every command, flag, exit code, and the protocols callers must follow.
Install
pip install cotype
Requires Python ≥ 3.11 and POSIX diff3 (from diffutils).
For development from a clone:
git clone https://github.com/yurug/cotype.git
cd cotype
pip install -e cli/
CLI surface
cotype init FILE [--json]
cotype open FILE [--json]
cotype save FILE --base-sha HASH [--actor ACTOR] [--json] < proposed
cotype status FILE [--json]
cotype resolve FILE [--actor ACTOR] [--json]
cotype cat-base FILE [--base-sha HASH]
| Command | What it does |
|---|---|
cotype init |
Start managing FILE: create the sidecar (.<basename>.cotype/) and capture the current contents as the first base snapshot. Idempotent. |
cotype open |
Capture a fresh base snapshot before you (or your agent) edit. Returns base_sha and a base_path to read the bytes from. |
cotype save |
Submit a proposed new version (stdin) against --base-sha. Outcome is direct / merged / noop / conflict. |
cotype status |
Report whether FILE is unmanaged, clean, or conflicted. Side-effect free; safe to poll. |
cotype resolve |
Clear a pending conflict by accepting FILE's current contents (after the user has edited out the diff3 markers). |
cotype cat-base |
Print a base snapshot's bytes to stdout. Useful in shell pipelines. |
cotype --help and cotype <subcommand> --help give the full per-command
descriptions plus a copy-pasteable shell template; that's the canonical
agent-discoverable surface.
save outcomes
mode |
meaning |
|---|---|
direct |
base matched current; proposed bytes written atomically. |
merged |
3-way merge produced a clean result; merged content written. |
noop |
proposed equals current; nothing to do. |
A conflict yields status: "conflict", exit code 1, and rewrites
FILE in place with diff3 markers (<<<<<<< / ======= / >>>>>>>).
Open FILE in your editor, remove the markers, save, then run
cotype resolve FILE. A forensic copy of the three sides is kept under
.<basename>.cotype/conflicts/<id>/ for diagnostics. Until resolve is
called, every cotype save returns ConflictPending.
--actor is a free-form label (e.g. emacs, agent:reviewer,
agent:formatter, me). Stored in the conflict metadata; never affects
semantics. There is no privileged actor — every caller plays by the same
rules.
Caller protocols
Editor
on file load:
response = cotype open FILE --json
buffer = read(response.base_path)
base_sha = response.base_sha
on save:
response = cotype save FILE --base-sha base_sha --actor emacs < buffer
case response.status:
saved -> base_sha = response.sha
conflict -> show response.conflict_path; do not mark buffer clean
Agent / process
meta=$(cotype open task.md --json)
base_sha=$(printf '%s' "$meta" | jq -r .base_sha)
base_path=$(printf '%s' "$meta" | jq -r .base_path)
my-agent < "$base_path" > /tmp/proposed
cotype save task.md --base-sha "$base_sha" --actor agent:reviewer < /tmp/proposed
The agent always reads from base_path, never from FILE directly —
otherwise a concurrent writer's bytes can sneak into the agent's "what I
edited from" without cotype noticing. The normative form and the
forbidden pattern that loses updates are at
../kb/spec/protocols.md.
Exit codes
| Code | Meaning |
|---|---|
| 0 | success |
| 1 | merge conflict |
| 2 | usage error |
| 3 | unmanaged or corrupt sidecar |
| 4 | unknown base |
| 5 | pending conflict |
| 6 | I/O error |
| 7 | merge tool error |
Stable error names
UsageError, UnsupportedFile, UnmanagedFile, CorruptSidecar,
UnknownBase, ConflictPending, IoError, MergeToolError,
InvalidUtf8. JSON shape (with --json):
{ "status": "error", "error": "<Name>", "message": "<detail>" }
Reducing false-positive conflicts
cotype's merge is line-based (POSIX diff3); independent edits within
the same hunk can spuriously conflict. Two cheap mitigations:
- Pad region boundaries. Diff3 needs ~2 unchanged lines between two edit zones to treat them as separate hunks. Insert blank lines or a stable sentinel comment between regions different actors own.
- Splice structurally in the harness. Parse the file into regions
(Markdown sections, top-level defs, JSON keys) and rewrite ONLY your
own region's bytes; everything else flows through unchanged from
base_path. Two actors editing two different regions then cannot conflict by construction. See../examples/headless-agents.shfor the reference Markdown recipe.
Architecture (one paragraph)
Pure leaves (hash, paths, errors), I/O primitives (lock,
atomic_write), persistence (store), the merge wrapper (merge),
command implementations under commands/, and a single cli.py that
wires argparse and the JSON envelope. Every mutating command holds an
exclusive flock on <sidecar>/lock; every file replacement goes
through tmp → fsync → rename → fsync(parent). 3-way merge invokes POSIX
diff3 -m in subprocess (list form, never shell). Public functions are
typed; files are under 200 lines; the test suite covers SPEC §14
conformance (T1–T10), every named property (P1–P15), security edges,
and a threaded atomic-visibility smoke test.
For the "why" behind these choices, see
../kb/architecture/decisions/.
Tests
cd cli
pip install pytest
pytest -q
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cotype-0.2.3.tar.gz.
File metadata
- Download URL: cotype-0.2.3.tar.gz
- Upload date:
- Size: 56.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4d76b69f878d3d3bb8bdc2951b2d1f63f10123425329785a3c06b41fee385b48
|
|
| MD5 |
7cb8c3783cdf2eb0da91c83b2aaf7f9c
|
|
| BLAKE2b-256 |
4caa58eef14e3d777c690fd61abb18d0ebbfe8c6e56f798c2cd853491e2b4957
|
Provenance
The following attestation bundles were made for cotype-0.2.3.tar.gz:
Publisher:
publish.yml on yurug/cotype
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cotype-0.2.3.tar.gz -
Subject digest:
4d76b69f878d3d3bb8bdc2951b2d1f63f10123425329785a3c06b41fee385b48 - Sigstore transparency entry: 1448773600
- Sigstore integration time:
-
Permalink:
yurug/cotype@79e1a8c7b30a5abdfe80ecf7644c0d5011a252e5 -
Branch / Tag:
refs/tags/v0.2.3 - Owner: https://github.com/yurug
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@79e1a8c7b30a5abdfe80ecf7644c0d5011a252e5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file cotype-0.2.3-py3-none-any.whl.
File metadata
- Download URL: cotype-0.2.3-py3-none-any.whl
- Upload date:
- Size: 50.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9f0825e5eb5a478deb918b3b00f9ab767269c76c81c3977f91ea5c813e0225c4
|
|
| MD5 |
486adc11c0947d952e4b3d44d0420580
|
|
| BLAKE2b-256 |
a5b7e763237ef30ae71846e41c1729b8a117297f096317676fdd8084025be801
|
Provenance
The following attestation bundles were made for cotype-0.2.3-py3-none-any.whl:
Publisher:
publish.yml on yurug/cotype
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cotype-0.2.3-py3-none-any.whl -
Subject digest:
9f0825e5eb5a478deb918b3b00f9ab767269c76c81c3977f91ea5c813e0225c4 - Sigstore transparency entry: 1448773681
- Sigstore integration time:
-
Permalink:
yurug/cotype@79e1a8c7b30a5abdfe80ecf7644c0d5011a252e5 -
Branch / Tag:
refs/tags/v0.2.3 - Owner: https://github.com/yurug
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@79e1a8c7b30a5abdfe80ecf7644c0d5011a252e5 -
Trigger Event:
push
-
Statement type: