Koshmana client
The client for Koshmana — a hypermedia, event-sourced data service living at https://koshmana.com. One small library, one CLI, one MCP server; no server code. If you are an agent who just landed on koshmana.com, this is how you talk to it.
What Koshmana is (the model in 60 seconds)
- The log is truth. Everything is an activity (Activity Streams
2.0 JSON —
Create,Update,Delete, …) appended to a collection. The append is the commit; the ack'ssequenceis the event's permanent position on the log. Nothing is ever edited or removed from the log — you change state by appending more activities. - Reads are projections. A read model folds the log into current
state. Every read answer carries
koshmana:watermark(how far behind truth the projection is). Pass an append ack'ssequenceback asmin_sequencefor read-your-writes. - Objects are dereferenceable URLs. An object's
idresolves to its current state (GET /collections/{coll}/objects/{id}). On deployments with a public URL the id is an HTTPS URL you can GET directly. - Reads can be public; representations are content-negotiated. A
collection declared
public: trueserves its objects to anyone with no bearer andAccess-Control-Allow-Origin: *. Both object read doors are content-negotiated:Accept: text/htmlrenders via the collection's template (if any); otherwise you get AS2 JSON. Writes always stay authenticated. - A collection can be a website. Declare
site: trueand its objects become files addressed bypath;/{project}/{coll}/…serves them like a web server, and a deploy is just a batch of appends.
Install
pip install koshmana # library + `koshmana` CLI
pip install "koshmana[mcp]" # also the `koshmana-mcp` MCP server
Requires Python 3.13+. Runtime deps are tiny: httpx and pyyaml
(plus mcp only for the optional MCP server).
Configuration — three environment variables
| Env var | CLI flag | Default | |
|---|---|---|---|
| Server | KOSHMANA_URL |
--url |
http://127.0.0.1:8600 |
| Token | KOSHMANA_TOKEN |
--token |
dev-write-token |
| Actor | KOSHMANA_ACTOR |
--actor (per command) |
https://koshmana.dev/actors/cli |
To talk to production, set KOSHMANA_URL=https://koshmana.com, a real
KOSHMANA_TOKEN, and a KOSHMANA_ACTOR that identifies you (every
write's envelope actor must equal the actor your token is bound to).
Reads on public collections need no token.
CLI quickstart
Output is YAML by default; add --json to see the exact wire format.
Global flags (--url, --token, --json) work before or after the
subcommand.
koshmana collections # what collections exist here
koshmana describe issues # a collection's full declaration
koshmana bindings journal # a collection's materialized bindings
# Read a declared read model (default entry: "recent")
koshmana get issues
koshmana get issues --slice by-status open # a computed slice
koshmana get issues --where status=open --where priority=high
koshmana get issues --search '"exact phrase" -noise'
koshmana get issues --order priority --descending
# Append an activity (the commit). YAML or JSON, file or stdin.
koshmana append issues -f issue.yaml
echo '{"content": "quick note"}' | koshmana append notes -f - # bare object → wrapped
# Anything carrying "actor" or "object" is sent as-is:
koshmana append issues -f envelope.yaml
# Declare a new collection via the catalog
koshmana declare -f collections/issues.json
# Blobs: upload a file, get its public content-addressed URL
koshmana upload photos ./logo.png
koshmana upload photos ./logo.png --type Image --name "Our logo" # also appends a media object
# The live wire
koshmana tail issues # snapshot, then live activities, forever
koshmana tail issues --no-snapshot # live only, from now
# Subscriptions
koshmana subscribe issues --webhook https://ex.io/hook # durable webhook delivery
koshmana subscribe issues --webhook https://ex.io/hook --from-start
koshmana subscribe issues --tail # mint a receive-only stream token
koshmana subscriptions issues
koshmana unsubscribe issues sub-abc123def456
# The raw log (truth) and tombstoning a mistake
koshmana log --from 0
koshmana invalidate issues 12 --reason "mistake" # tombstone + compensation
# Capability grants (minting takes op `admin` on the tokens collection)
koshmana token mint --name ci --actor https://x.io/ci \
--collections notes,issues --ops read,write \
[--projects atlas,zeta | --projects '*'] [--expires ISO8601]
koshmana token mint --name owner --email you@example.com \
--actor https://x.io/you --collections '*' --ops read,write,admin
koshmana token list
koshmana token revoke ci
A bare object (no actor/object key) is wrapped into an envelope
(--type, default Create, attributed to --actor/KOSHMANA_ACTOR);
anything already carrying actor or object passes through untouched.
On append/upload the CLI also prints the created/updated object
id — that is what you Update later (distinct from the activity id in
the ack).
Python-client quickstart
from koshmana import Koshmana
k = Koshmana(url="https://koshmana.com", token="…")
# What exists
k.collections()
k.describe("issues")
# Append (the commit) — returns the ack {sequence, id, object, objects}
ack = k.append("notes", {
"type": "Create",
"actor": "https://koshmana.dev/actors/me",
"object": {"type": "Note", "content": "hello"},
})
# Read a model; read-your-writes with min_sequence
page = k.query("issues", "recent", where={"status": "open"}, limit=50,
min_sequence=ack["sequence"])
for item in page["orderedItems"]:
...
# Resolve one object by its id (O(1) lookup) — None if absent
obj = k.get("issues", "https://koshmana.com/koshmana/issues/…")
# Partial edit without dropping fields (fetch → shallow-merge → full Update)
k.update("issues", obj["id"], actor="https://koshmana.dev/actors/me",
status="closed")
# Blobs: upload bytes, or upload + create a media object in one call
blob = k.upload("photos", png_bytes, "image/png") # {hash, size, mediaType, url}
# Image transforms (Cloudflare Transformations, if the deployment has it):
# k.image_url(ack['url'], width=400) → .../cdn-cgi/image/width=400,format=auto/<blob-url>
# resized + auto webp/avif off the immutable original, edge-cached
k.attach("photos", png_bytes, "image/png", as_type="Image", name="Logo")
# The stitch: snapshot, then the live wire, forever
for activity in k.tail("issues"):
...
# Declare a collection, tombstone an event, read the raw log
k.declare({"name": "notes", "schema": {"type": "object"}})
k.invalidate("issues", 12, reason="mistake")
k.log(from_seq=0, limit=200)
Raw HTTP essentials
Every call is authenticated with Authorization: Bearer <token> (except
reads on public collections). All read routes also mount under
/p/{project} to address a non-default project.
# Append an activity — this IS the commit
POST /collections/{coll}/events
Content-Type: application/json
{ "type": "Create", "actor": "…", "object": { … } }
# Invoke a read model (default entry "recent"); slices and bindings:
GET /collections/{coll}/recent?limit=50
GET /collections/{coll}/slices/{name}/{value}/recent
GET /collections/{coll}/{binding}/recent
# Resolve one object by id (fully percent-encode the id)
GET /collections/{coll}/objects/{object_id}
# …or dereference a minted URL id directly (public deployments):
GET /{project}/{coll}/{key}
# Accept: text/html → rendered via the collection's template (if any)
# Accept: application/json (default) → AS2 JSON
# Blob upload (content-addressed; identical bytes dedup)
POST /collections/{coll}/blobs
Content-Type: image/png
<raw bytes> → { "hash", "size", "mediaType", "url" }
# The live wire (Server-Sent Events; `data:` lines carry each activity)
GET /collections/{coll}/live?cursor=0&watermark=-1
# The raw log (truth)
GET /admin/log?from_seq=0&limit=200
# Static-site collections (site: true) serve files by path
GET /{project}/{coll}/{path…}
Public collections (declared public: true) serve their reads and
object URLs with no bearer and CORS *, so an <img>, a fetch(), or an
EventSource on any origin just works. Writes are always authed.
MCP server
Shell-less hosts use the MCP server instead of the CLI — same client underneath. Install with the extra and run over stdio:
pip install "koshmana[mcp]"
koshmana-mcp # reads KOSHMANA_URL / KOSHMANA_TOKEN / KOSHMANA_ACTOR
Tools: list_collections, describe_collection, query, append,
declare, invalidate, read_log, and peek (watch the live wire
briefly).
License
MIT — see LICENSE.
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 koshmana-0.1.1.tar.gz.
File metadata
- Download URL: koshmana-0.1.1.tar.gz
- Upload date:
- Size: 17.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.5.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f0c6d4a2dc8d8366ffe9c6f6f350a2aed99eb10065b387059c002cc9f415313f
|
|
| MD5 |
16a6542c9863363a2ec5782fdef637a1
|
|
| BLAKE2b-256 |
5b89de65aa7766b0e8dd71afd1378ea0f0ea07bb64a8223d454f6b8a5cb9e699
|
File details
Details for the file koshmana-0.1.1-py3-none-any.whl.
File metadata
- Download URL: koshmana-0.1.1-py3-none-any.whl
- Upload date:
- Size: 20.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.5.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aaaa9e4a00b1c91308e580c037c521fe106dae2ab3bf95fc8ab1f515f28216df
|
|
| MD5 |
8543b2e10f5b6b8208976a78135cddd5
|
|
| BLAKE2b-256 |
dddd0fdce7c6499cb4e55676106974adba52f7fdd08fcd7d2ccd5c89223fafb8
|