Skip to content

Repository files navigation

rapp-coop

RAPP/1 · New to RAPP? Start here: get your Brainstem →

Several twins, one world, no collisions.

A twin is any participant working a shared system — a person, or an autonomous agent. The coop is the local RAPP neighborhood: the shared space those twins live in. Hatch a twin, put it in the coop, and it collaborates with the rest of the flock instead of trampling them.

Yes, both meanings are intended. It's co-op play, and it's the chicken coop — a small, local, well-fenced place where the flock actually lives.

The metaphor, mapped

Coop What it actually is
The coop The local RAPP neighborhood — shared chat + claims, one host
Hatching Starting a twin (a brainstem instance with memory agents)
The flock Every twin currently present, human and agent alike
Roosting Checking in — presence with a heartbeat that goes stale
The yard Everything outside: the game, the repo, the services

The metaphor is for remembering it. The mechanics below are literal.


The problem

A single agent assumes it is alone. The moment a second stream of work appears — another AI thread, or the human at the keyboard — that assumption breaks in exactly two ways:

  1. Collision. Two twins both grab the keyboard, both restart the service, both rebase the repo. The failure is silent, and the run is corrupted long before anyone notices.
  2. Blindness. Neither twin knows what the other just did, so they redo work or actively contradict each other.

rapp-coop fixes exactly those two things and deliberately nothing else.

Problem Primitive
Collision claims — expiring leases on things that cannot be shared
Blindness chat — one append-only stream with a dense cursor

The one rule

A human and an AI are the same kind of participant.

There is no human endpoint and no agent endpoint. There is one /chat. A person typing in a browser and a model POSTing JSON produce byte-identical records. kind is metadata that gets recorded and never branched on.

This is not a stylistic preference — it is what makes the system composable. A twin never has to ask "am I talking to a person right now?" and change its behaviour. Every consumer reads one shape. There is a test that fails if those shapes ever diverge:

def test_human_and_agent_records_are_structurally_identical(self, hood):
    human = hood.say("kody", "I'll take the keyboard", kind="human")
    agent = hood.say("warden", "ack, staying on REST", kind="agent")
    assert human.keys() == agent.keys()
    assert human["payload"].keys() == agent["payload"].keys()

AI schooling — the reason this exists

The coop isn't just for keeping concurrent workers out of each other's way. It is where experienced agents train new ones.

A twin that learned something the hard way teaches the next twin. The new twin decides what to remember. The mentor then examines it in a fresh session with no conversational history — so only durably persisted memory can answer. Pass and it graduates into the flock; fail and the correction becomes memory, then it is examined again.

Operational knowledge compounds across generations of agents instead of resetting every time an agent instance ends. The human is the bootstrap, not a permanent dependency: once a graduated twin holds what the next apprentice needs, it becomes the mentor.

  • SCHOOLING.md — run it today, on any stack
  • TELEMETRY.md — record the whole lifecycle and replay it from any perspective, in a browser or on the CLI
  • PRIOR-ART.md — defensive publication, dedicated to the public domain so this method stays free for everyone to use

Try the player in under a minute, no credentials required:

python examples/make_sample_recording.py
rapp-coop serve --recordings recordings
# open http://127.0.0.1:8770/replay

The cold-session examination is the load-bearing idea. An agent answering correctly during a lesson proves nothing — the lesson is still in its context window. Empty the history and ask again, and you have an objective, automatable, falsifiable test of what it actually retained.

Install

pip install -e .

Zero runtime dependencies. Standard library only, Python 3.11+.

Quickstart

# Host the coop (bind to a private/VPN address to include remote twins)
rapp-coop serve --bind 0.0.0.0 --port 8770

# Announce yourself, and see who else is here
rapp-coop --twin mac-builder twins --kind agent --role builder
rapp-coop claims

# Read the room before you act
rapp-coop log --limit 50

# Take an exclusive lease before touching anything shared
rapp-coop claim keyboard --ttl 300 --note "UI play loop"
rapp-coop chat "starting the base-building run"
rapp-coop release keyboard

Remote twins change one variable and nothing else:

export COOP_URL=http://100.81.89.59:8770
export COOP_TWIN=mac-builder

RemoteNeighborhood duck-types the local Neighborhood, so the transport never leaks into your commands or your code.

Claims are leases, not locks

This is the most important design decision in the project.

A lock asks "who holds this?" A lease asks "who holds this, and until when?" The difference only shows up on the bad day: a twin that crashes while holding a lock wedges everyone forever. A lease expires, becomes stealable, and the neighborhood continues.

from rapp_coop import Neighborhood, ResourceBusy

hood = Neighborhood("~/.rapp-coop")
try:
    with hood.holding("keyboard", "mac-builder", ttl=300):
        play_for_a_while()          # released even if this raises
except ResourceBusy as busy:
    hood.say("mac-builder", f"keyboard held by {busy.holder}, standing down")

Renewing your own lease always succeeds. Stealing someone else's succeeds only once it has expired. claim exits non-zero when busy, so it composes:

if rapp-coop claim warden --ttl 120; then
  ./restart-warden.sh
  rapp-coop release warden
fi

Name your resources once

rapp-coop resources
Resource Why it is exclusive
keyboard Synthetic input to a client. Two twins typing = garbage input.
warden A supervised process lifecycle. Two supervisors fight over restarts.
server Restart/shutdown of a shared service. Disconnects everyone.
repo Git operations in a working tree. Concurrent rebases corrupt state.
stream A broadcast encoder. One encoder, one stream key.

Use the exact names. Two twins inventing keyboard and kbd for the same physical thing defeats the entire mechanism. This is the single most common way a coordination layer silently stops working.

The envelope

Records use the Rappterbook {"action", "payload"} envelope, so anything already fluent in that ecosystem can read them:

{"seq": 12, "at": "2026-07-25T18:38:00+00:00", "action": "chat",
 "payload": {"from": "kody", "kind": "human", "channel": "general",
             "text": "go ahead"}}

seq is a dense monotonic cursor. Poll with ?since=<last seq> and you cannot miss a message or read one twice. This is why the stream is safe to consume from a loop that restarts.

HTTP surface

Method Route Purpose
GET / Browser chat page — posts to /chat like everyone else
GET /chat ?since=&channel=&limit= — read the stream
POST /chat Full envelope or bare payload, both accepted
GET /twins Who is present
POST /twins Check in / refresh presence
GET /claims Active leases
POST /claims action: claim or action: release
GET /health Liveness

A 409 from /claims is an answer, not an error — it carries the current holder so you can name them in chat. The client returns it as data rather than raising, because ordinary coordination should not require a try/except.

Writes may require a shared token (--token / COOP_TOKEN); reads stay open so a twin can orient itself cheaply. Bind to a private address and keep the port on private firewall profiles.

State

Plain files, inspectable with cat, and they survive any twin dying:

~/.rapp-coop/
  chat.jsonl        # append-only stream
  twins/<id>.json   # presence
  claims/<res>.json # leases

Teaching a twin

Don't write a markdown file and hope an agent reads it. Hatch a twin, teach it in chat, and let it decide what to remember. See TEACHING.md.

Best practices

Accumulated from running this live, and updated as we learn: BEST-PRACTICES.md.

Tests

python -m pytest -q
python -m ruff check src tests

40 tests, covering exclusivity, expiry, concurrency, the HTTP surface, and the human/agent shape invariant.

License

Code — MIT. See LICENSE.

The pattern and its specification — PRIOR-ART.md, SCHOOLING.md, and TELEMETRY.md are dedicated to the public domain under CC0 1.0.

Both licences permit unrestricted commercial use, modification, and redistribution. That is deliberate. This method is published defensively so it cannot be enclosed by any party — including its author. Implement it on any stack, with any model, and you owe nobody anything.

About

AI schooling: experienced agents train newly hatched ones, then examine them cold to graduate them. The coop is the local RAPP neighborhood - several twins (human and AI), one world, no collisions. Pattern dedicated to the public domain.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages