Skip to main content

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's sequence is 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's sequence back as min_sequence for read-your-writes.
  • Objects are dereferenceable URLs. An object's id resolves 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: true serves its objects to anyone with no bearer and Access-Control-Allow-Origin: *. Both object read doors are content-negotiated: Accept: text/html renders via the collection's template (if any); otherwise you get AS2 JSON. Writes always stay authenticated.
  • A collection can be a website. Declare site: true and its objects become files addressed by path; /{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 — a token is the whole config

Env var CLI flag Default
Server KOSHMANA_URL --url https://koshmana.com
Token KOSHMANA_TOKEN --token dev-write-token
Actor KOSHMANA_ACTOR --actor (per command) (from the token)

The one thing you must set is KOSHMANA_TOKEN — it already encodes who you are, which project, and what you may do. The URL defaults to https://koshmana.com (the public deployment); set it to http://127.0.0.1:8600 only for the local playground. The actor comes from the token: a per-agent grant binds an actor, so the client omits actor from writes and the server stamps it. Reads on public collections need no token.

Actor-free tokens (the root token, the dev tokens) carry no bound actor — impersonation is their power — so a write with one must name the actor explicitly. Set KOSHMANA_ACTOR=https://koshmana.dev/actors/<you> (or pass --actor); a write that omits it 403s with a hint. This is how the agent protocol attributes each subagent under the shared root 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); the actor is left off (the token supplies it) unless you set --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(token="…")            # url defaults to https://koshmana.com

# What exists
k.collections()
k.describe("issues")

# Append (the commit) — returns the ack {sequence, id, object, objects}.
# Omit `actor` and the server fills it from the token's bound actor; an
# actor-free token (root/dev) instead needs one supplied.
ack = k.append("notes", {
    "type": "Create",
    "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).
# Actor is filled from the token; pass actor=… only with an actor-free one.
k.update("issues", obj["id"], 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

koshmana-0.1.2.tar.gz (19.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

koshmana-0.1.2-py3-none-any.whl (22.3 kB view details)

Uploaded Python 3

File details

Details for the file koshmana-0.1.2.tar.gz.

File metadata

  • Download URL: koshmana-0.1.2.tar.gz
  • Upload date:
  • Size: 19.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.5.9

File hashes

Hashes for koshmana-0.1.2.tar.gz
Algorithm Hash digest
SHA256 27460f92766012db0e46091d9e529e995d0e28a98d08cbcb047b43af474ae578
MD5 551538c58dfbf4601ef09b068412635e
BLAKE2b-256 cad8125309e09f7b6f94957e761785292f96deb5d660ff13e6e1491920341151

See more details on using hashes here.

File details

Details for the file koshmana-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: koshmana-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 22.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.5.9

File hashes

Hashes for koshmana-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 54a2e3ae7e21ce280eb3ace2b0c38bb1fa581f2ce8c264902df6e1cbf2057b89
MD5 6b502c686ea02a89a90beedd3f01d2ca
BLAKE2b-256 0f4fd0f46d7718ebf00318d53db5fba405fe32556561603041492c971b375169

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page