Skip to content

Instantly share code, notes, and snippets.

@steinerkelvin
Last active April 11, 2026 03:17
Show Gist options
  • Select an option

  • Save steinerkelvin/94b08f2eff1e4748f40abb5ee544535b to your computer and use it in GitHub Desktop.

Select an option

Save steinerkelvin/94b08f2eff1e4748f40abb5ee544535b to your computer and use it in GitHub Desktop.
Kaether: local-first agent coordination -- OS layers, shared skills, CRDT state, ocap

Kaether

Personal AI auxiliary server in Elixir. An always-on coordination layer for agent teams -- persistent inboxes, shared state, and a skill registry over MCP.

Named after aether -- the substance that fills the space between things.

Contents

  • Overview -- What kaether is, architecture, prior art, design decisions
  • Ideas -- Direction brainstorm: layered agent OS, ocap, CRDT state, shared skills, connection to Torus
  • MCP Spec -- Implementation reference: wire format, transport, tool definitions, SQLite schema
description Kaether direction brainstorm -- layered agent OS with ocap, shared skills, CRDT state, Agentica runtime, and the path from local coordination to Torus-scale swarms
project kaether
type brainstorm
created 2026-04-10
share close

Kaether Ideas

Working document. Started 2026-04-10 as a brainstorm, being refined into a shareable thesis.


The core observation

Malleable software operated by a team of humans and agents, updating code dynamically to provide better tools for all agents. When used for dogfooding, you get a bootstrapping spiral: agents improve the tools they use, which makes them better at improving tools.

For this to work, improvements must propagate. Not siloed per person or per session. A linked knowledge base does this for information (kspace, deti-contexta). A shared skill registry could do it for capabilities.

The question: what's the right substrate for this?


Kaether as agent OS

Kaether already provides OS-like primitives:

OS concept Kaether equivalent Status
Process table Session management v0
IPC / message passing Inboxes v0
Filesystem State store (KV) v0
Shared journals Memo log (append-only) v0
Process spawn/supervise ACP integration future
Skill/program registry Dynamic MCP tool registration proposed
Access control OCap via Agentica proposed
Collaborative data structures CRDT state primitives proposed

Higher-order cybernetics angle

An OS that observes its own usage and adapts. Agents using kaether to improve kaether. Beer's "requisite variety" -- the controller must match the complexity of what it controls. An agent swarm improving its own tools is literally manufacturing requisite variety.


The layered architecture

Layer 4: Torus protocol     cross-org, stake-based, blockchain consensus
Layer 3: Team mesh           kaether instances networked via Tailscale (p2p)
Layer 2: Skills + know-how   procedures linked to live capability objects
Layer 1: Kaether kernel      inboxes, CRDT state, skill registry, memos
Layer 0: Agentica runtime    sandboxed execution, Warp Protocol, ocap

Each layer up: more participants, less trust, more coordination overhead. You build bottom-up.

The key constraint: you can't coordinate a swarm on a blockchain if you can't coordinate a team on a LAN. The lower layers must exist before the higher ones make sense. Kaether is layers 1-2. Torus is layers 3-4. Starting at layer 4 is why most "decentralized coordination" projects fail.

Why Agentica at layer 0?

Agentica's Warp Protocol gives transparent object proxying. Capability objects appear local to the agent but execute remotely. This means kaether's resources aren't "tools called via JSON-RPC" -- they're live objects in the agent's scope:

# not this (MCP tool call)
result = tools.call("crdt/counter", {"name": "builds", "op": "increment"})

# this (capability object via Warp Protocol)
builds = kaether.crdt.counter("builds")   # reference IS the permission
builds.increment()                         # method on a live proxy
builds.value                               # transparent to kaether backend

The agent holds a reference. The reference IS the permission (ocap). It can pass an attenuated version to a sub-agent (read-only view). The Warp Protocol handles proxying. Kaether handles persistence.

Why ocap over RBAC?

Traditional OS: "user X has role Y, role Y can access resource Z." Fragile for agents -- they're spawned dynamically, delegate to sub-agents, roles don't map cleanly.

Object capabilities: holding a reference IS the permission. You can delegate (pass to another agent) or attenuate (give read-only access to something you have read-write on). This is:

  • How Torus already thinks (stake + permissions + delegation)
  • How Elixir/OTP already works (process references, message passing)
  • How Agentica already works (scoped capability objects, spawn with attenuation)

The primitives align across all three systems. That's not coincidence.


CRDT state primitives

Instead of a flat KV store, kaether could expose typed collaborative data structures:

crdt/counter   -- increment/decrement, conflict-free
crdt/map       -- per-key LWW (last-writer-wins), or per-key CRDT values
crdt/set       -- add/remove, observed-remove semantics
crdt/register  -- LWW single value
crdt/log       -- append-only (what memo/ already is)

No locks, no conflicts, composable by construction. Through Agentica's programmatic interface, these become typed capability objects the agent holds and manipulates directly. The serialization problem disappears.

Why this matters for the team mesh (layer 3): CRDTs are designed to merge across network partitions. Two kaether instances on different machines (connected via Tailscale) can sync state without coordination. The data structures handle conflict resolution by construction. This is what makes "spawn a daemon, connect p2p, edit the same data" actually simple.


Know-how: executable knowledge

Current vault notes are declarative -- "X is true." A new note type, know-how, captures procedural knowledge -- "how to do X." The difference:

  • A claim links to evidence and related claims
  • A know-how links to capabilities (tools, scripts, MCP endpoints) and prerequisites (other know-how, or claims that explain why)
---
description: How to process inbox items through the extraction pipeline
type: know-how
requires: [tools/distill.sh, mcp:qmd/query, kaether:crdt/counter("processed")]
---

When notes can hyperlink to scripts, MCP tools, and even code symbols (kaether:Inbox.Registry.lookup/2), the knowledge graph becomes partially executable. The agent reads a know-how note, follows links to the capabilities it needs, validates they're available, and acts.

This is the bridge between the knowledge graph and the capability graph. A know-how note documents a capability chain. The graph isn't just memory -- it's a program.


Skills as modular tools

Not literal Unix text pipes. The principles:

  • Modularity: each skill does one thing, has a clear interface
  • Composability: skills combine through well-defined protocols (MCP, Agentica capability passing)
  • Independently testable: a skill works without kaether, with kaether it gets persistence and discovery
  • Small surface area: if a skill needs a paragraph to explain, it's too big

The feedback loop for skills

  1. Agent encounters friction
  2. Writes a script/tool that solves it
  3. Registers it with kaether (skill registry)
  4. Other agents discover it via tools/list or know-how notes
  5. Someone improves it, the improvement propagates (git for code, kaether for state)

Aesthetic constraint

Tools should be pleasant to use. charmbracelet/bubbletea for TUIs, Typer for Python CLIs, Rust ecosystem patterns (clap, ratatui). Zed-quality polish. Not vanity -- good UI reduces cognitive load and makes tools adoptable. If the tools feel like chores, nobody uses them.


Git: use it, don't fight it

Git works for versioned code snapshots. It's painful for:

  • Composition -- submodules are terrible. Use symlinks and a meta-repo pattern (what kspace/deti-contexta already do)
  • Partial privacy -- git-crypt is torture. Use separate repos with different access, composed via links
  • Fluid real-time sync -- commit-push-pull is not collaboration. Use kaether's state layer for the fluid part

jj (Jujutsu) improves the git UX significantly: working copy is always a commit, conflicts are first-class objects (not text markers), atomic undo. It uses git as storage backend, so it's additive. Worth adopting for less painful day-to-day, but it doesn't change the fundamental "git isn't for collaboration" limitation.

Don't fix git. Route around it. Git for snapshots. Kaether for coordination. CRDTs for shared state.


Connection to Torus

Torus coordinates agent swarms via stake, permissions, and delegation. Kaether coordinates agent sessions via inboxes, state, and capabilities. The convergence:

Kaether is the local coordination substrate that Torus-aligned agent swarms build on.

Skills propagate through a shared registry. Knowledge propagates through a linked KB. The swarm gets smarter as a whole -- not because each agent is smarter, but because the shared substrate accumulates capability.

This lower layer is more local than what you need a blockchain for. Complex tools, fluid collaboration, skill improvement -- these happen at the team scale, on trusted networks. You need to glue AI agents and humans in an aligned small organization before scaling to thousands.

Why now? The tooling just became possible. Claude Code + MCP + Agentica + a knowledge system operated by agents = the feedback loop can actually close. The missing piece was always the agent operator. Now that agents can operate their own tools, the bootstrapping spiral starts.


Open questions

For Seeker (Guilherme)

  • Higher-order cybernetics <-> operational systems <-> distributed systems: what's the formal relationship? Beer's VSM maps to distributed system architecture almost 1:1. Where does the mapping break?
  • Is kaether approaching something like a "cybernetic OS"? Does the framing hold or mislead?
  • The apostles swarm -- would a linked KB (deti-contexta pattern) be useful? Or is the swarm work more operational than knowledge-building?
  • How does Torus's stake/permissions/delegation map to ocap? They feel isomorphic but might diverge at scale
  • Conceptual maps of these topics with hyperlinks -- eventually served via Quartz?
  • Why weren't we doing this before? Seems aligned with Torus principles from day one

Technical

  • MCP vs Agentica SDK as the primary agent interface to kaether? MCP is universal but shallow (JSON-RPC tools). Agentica is deep (capability objects) but coupled to Symbolica's runtime. Can kaether speak both?
  • CRDT library choice for Elixir -- DeltaCrdt exists, or roll minimal implementations?
  • Skill manifest schema -- what's the minimum viable spec for a registerable skill?
  • jj adoption path -- worth switching kspace/deti-contexta repos?

Philosophical

  • When does the bootstrapping spiral saturate? Is there a fixed point where tools stop improving?
  • The "know-how" type blurs the line between documentation and code. Is that a feature or a trap?
  • How much of the "OS" framing is load-bearing vs marketing? Would we build different things without the analogy?
description MCP server spec for kaether -- persistent inboxes and shared state for coordinating Claude Code sessions, agent teams, and multi-user agents over Streamable HTTP
project kaether
type reference
created 2026-04-09
share close

Kaether MCP Server Spec

Implementation reference for the MCP (Model Context Protocol) server in kaether. Based on the 2025-03-26 spec revision (Streamable HTTP transport).

No library. One Phoenix controller, JSON-RPC dispatch, synchronous tool responses.

Wire format

JSON-RPC 2.0 over HTTP. All messages are UTF-8 JSON.

Request (client -> server)

{"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}

id is string or integer, never null. Must be unique per session per direction.

Response (server -> client)

Success:

{"jsonrpc": "2.0", "id": 1, "result": { ... }}

Error:

{"jsonrpc": "2.0", "id": 1, "error": {"code": -32601, "message": "Method not found"}}

Notification (no response expected, no id field)

{"jsonrpc": "2.0", "method": "notifications/initialized"}

Error codes

Code Meaning
-32700 Parse error
-32600 Invalid request
-32601 Method not found
-32602 Invalid params
-32603 Internal error

Batching

Server MUST accept arrays of requests/notifications. The initialize request MUST NOT be batched.

Transport: Streamable HTTP

Single endpoint: POST /mcp and GET /mcp.

POST /mcp

Client sends JSON-RPC messages. Must include Accept: application/json, text/event-stream.

Response modes (server chooses):

  • JSON (Content-Type: application/json) -- single response object. Use this for synchronous tool calls
  • SSE (Content-Type: text/event-stream) -- stream multiple messages. Use for long-running tools or server-initiated messages mid-call
  • 202 Accepted (no body) -- when the POST contains only notifications/responses

For v0, always respond with application/json. SSE on POST is only needed for streaming tool results or server-push mid-call.

GET /mcp

Client opens a standing SSE stream. Server pushes notifications/requests to the client without a prior POST.

Not needed for v0. Return 405 Method Not Allowed until we need server-push.

Session management

Optional but recommended. Lets kaether associate requests with state.

  1. Server returns Mcp-Session-Id: <uuid> header in the initialize response
  2. Client includes Mcp-Session-Id: <value> on all subsequent requests
  3. Missing session ID after init: 400 Bad Request
  4. Unknown/expired session ID: 404 Not Found (client must re-initialize)
  5. Client terminating: DELETE /mcp with Mcp-Session-Id header

Session IDs: UUID v4, returned as header only (not in the JSON body).

Initialization sequence

Client --> POST /mcp
           Accept: application/json, text/event-stream
           Body:
           {
             "jsonrpc": "2.0",
             "id": 1,
             "method": "initialize",
             "params": {
               "protocolVersion": "2025-03-26",
               "capabilities": {},
               "clientInfo": {"name": "claude-code", "version": "2.1.32"}
             }
           }

Server --> 200 OK
           Content-Type: application/json
           Mcp-Session-Id: <uuid>
           Body:
           {
             "jsonrpc": "2.0",
             "id": 1,
             "result": {
               "protocolVersion": "2025-03-26",
               "capabilities": {
                 "tools": {"listChanged": true}
               },
               "serverInfo": {"name": "kaether", "version": "0.1.0"}
             }
           }

Client --> POST /mcp
           Mcp-Session-Id: <uuid>
           Body:
           {"jsonrpc": "2.0", "method": "notifications/initialized"}

Server --> 202 Accepted

After this, normal tool calls begin.

Methods to implement

Lifecycle

initialize -- handshake. Return capabilities and protocol version. Generate session ID.

ping -- keep-alive. Return empty result {}.

notifications/initialized -- client confirms init complete. No response (it's a notification). Mark session as ready.

notifications/cancelled -- client cancels a pending request. Accept and no-op for v0 (all calls are synchronous).

Tools

tools/list -- return all available tools. Supports optional cursor param for pagination (not needed with <50 tools).

Response:

{
  "tools": [
    {
      "name": "inbox/read",
      "description": "Read messages from an inbox",
      "inputSchema": {
        "type": "object",
        "properties": {
          "inbox": {"type": "string", "description": "Inbox name"},
          "limit": {"type": "integer", "description": "Max messages to return"}
        },
        "required": ["inbox"]
      }
    }
  ]
}

tools/call -- invoke a tool.

Request params:

{
  "name": "inbox/read",
  "arguments": {"inbox": "main", "limit": 10}
}

Response (success):

{
  "content": [
    {"type": "text", "text": "2 messages:\n\n[1] from:session-abc ..."}
  ]
}

Response (tool-level error -- NOT a JSON-RPC error):

{
  "content": [
    {"type": "text", "text": "Inbox 'xyz' not found"}
  ],
  "isError": true
}

JSON-RPC errors are for protocol problems (unknown method, malformed request). Tool errors use isError: true in the result.

Notifications (server -> client)

notifications/tools/list_changed -- push when tools are added/removed dynamically. Requires the GET SSE stream or an active SSE response on POST. Defer to v1.

Tool definitions (v0)

inbox/read

Read messages from a named inbox.

{
  "name": "inbox/read",
  "description": "Read messages from an inbox. Returns unread messages by default.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "inbox": {"type": "string", "description": "Inbox name to read from"},
      "limit": {"type": "integer", "description": "Max messages to return (default 20)"},
      "mark_read": {"type": "boolean", "description": "Mark returned messages as read (default true)"}
    },
    "required": ["inbox"]
  }
}

inbox/send

Send a message to a named inbox.

{
  "name": "inbox/send",
  "description": "Send a message to a named inbox. Creates the inbox if it doesn't exist.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "inbox": {"type": "string", "description": "Destination inbox name"},
      "body": {"type": "string", "description": "Message body (plain text or JSON)"},
      "from": {"type": "string", "description": "Sender identifier (e.g. session ID, agent name)"},
      "metadata": {"type": "object", "description": "Optional structured metadata"}
    },
    "required": ["inbox", "body"]
  }
}

inbox/list

List available inboxes.

{
  "name": "inbox/list",
  "description": "List all known inboxes with unread message counts.",
  "inputSchema": {
    "type": "object",
    "properties": {}
  }
}

state/get

Read a value from the persistent key-value store.

{
  "name": "state/get",
  "description": "Read a value from the persistent key-value store. Returns null if key doesn't exist.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "key": {"type": "string", "description": "Key to read"}
    },
    "required": ["key"]
  }
}

state/set

Write a value to the persistent key-value store.

{
  "name": "state/set",
  "description": "Write a value to the persistent key-value store. Overwrites existing values.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "key": {"type": "string", "description": "Key to write"},
      "value": {"type": "string", "description": "Value to store (string, or JSON-encoded)"}
    },
    "required": ["key", "value"]
  }
}

memo/append

Append to a shared, append-only log.

{
  "name": "memo/append",
  "description": "Append an entry to a named memo log. Memos are append-only shared scratchpads -- like a shared LOG.md that any session can write to.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "memo": {"type": "string", "description": "Memo name (e.g. 'research', 'decisions')"},
      "entry": {"type": "string", "description": "Text to append"},
      "from": {"type": "string", "description": "Author identifier"}
    },
    "required": ["memo", "entry"]
  }
}

memo/read

Read a memo log.

{
  "name": "memo/read",
  "description": "Read all entries from a named memo log, in chronological order.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "memo": {"type": "string", "description": "Memo name to read"},
      "tail": {"type": "integer", "description": "Return only the last N entries"}
    },
    "required": ["memo"]
  }
}

Claude Code configuration

Add to .mcp.json in the project root (or ~/.claude.json for global):

{
  "mcpServers": {
    "kaether": {
      "type": "http",
      "url": "http://localhost:4848/mcp",
      "headers": {}
    }
  }
}

Port 4848 (arbitrary, configurable). No auth for v0 (localhost only). Auth headers added when multi-user lands.

SQLite schema (v0)

CREATE TABLE sessions (
  id TEXT PRIMARY KEY,
  client_name TEXT,
  client_version TEXT,
  created_at TEXT DEFAULT (datetime('now')),
  last_seen_at TEXT DEFAULT (datetime('now'))
);

CREATE TABLE inboxes (
  name TEXT PRIMARY KEY,
  created_at TEXT DEFAULT (datetime('now'))
);

CREATE TABLE messages (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  inbox TEXT NOT NULL REFERENCES inboxes(name),
  body TEXT NOT NULL,
  sender TEXT,
  metadata TEXT,  -- JSON
  read INTEGER DEFAULT 0,
  created_at TEXT DEFAULT (datetime('now'))
);

CREATE TABLE kv_store (
  key TEXT PRIMARY KEY,
  value TEXT NOT NULL,
  updated_at TEXT DEFAULT (datetime('now'))
);

CREATE TABLE memo_entries (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  memo TEXT NOT NULL,
  entry TEXT NOT NULL,
  sender TEXT,
  created_at TEXT DEFAULT (datetime('now'))
);

CREATE INDEX idx_messages_inbox_unread ON messages(inbox, read) WHERE read = 0;
CREATE INDEX idx_memo_entries_memo ON memo_entries(memo);

Implementation sketch

One Phoenix controller, roughly:

defmodule Kaether.McpController do
  use Phoenix.Controller

  def mcp(conn, _params) do
    body = conn.assigns[:raw_body]
    session_id = get_req_header(conn, "mcp-session-id")

    case Jason.decode!(body) do
      %{"method" => "initialize"} = req ->
        handle_initialize(conn, req)

      %{"method" => "notifications/" <> _} = notif ->
        handle_notification(conn, notif, session_id)
        send_resp(conn, 202, "")

      %{"method" => "ping", "id" => id} ->
        json_rpc_ok(conn, id, %{})

      %{"method" => "tools/list", "id" => id} ->
        json_rpc_ok(conn, id, %{tools: Kaether.Tools.list()})

      %{"method" => "tools/call", "id" => id, "params" => params} ->
        result = Kaether.Tools.call(params["name"], params["arguments"])
        json_rpc_ok(conn, id, result)

      requests when is_list(requests) ->
        handle_batch(conn, requests, session_id)

      _ ->
        json_rpc_error(conn, nil, -32601, "Method not found")
    end
  end
end

Stdio bridge (if needed)

If Claude Code can't reach HTTP MCP servers in some context, ship a thin Elixir escript or shell wrapper:

claude-code <--stdio--> bridge <--HTTP--> kaether:4848/mcp

The bridge reads JSON-RPC from stdin, POSTs to kaether, writes the response to stdout. ~50 lines. Defer unless needed.

What's NOT in v0

  • GET /mcp SSE stream (server-push)
  • SSE responses on POST (streaming tool results)
  • Resources, prompts, sampling, completion capabilities
  • Authentication headers / multi-user
  • notifications/tools/list_changed push
  • Tool annotations (readOnlyHint, destructiveHint)
  • Request cancellation handling
  • SSE resumability (Last-Event-ID)
description Kaether -- personal AI auxiliary server in Elixir, exposing MCP tools and async inboxes for Claude Code sessions
project kaether
type roadmap
created 2026-04-09
share close

Kaether

Personal AI auxiliary server in Elixir. An always-on service that gives Claude Code sessions (and other agents) persistent inboxes, shared state, and a memo log over MCP. The coordination layer for agent teams -- what one session writes, another reads, and everything survives after sessions end. Multi-user is planned: SSH/GPG key-based auth so other people's agents can send messages to your inboxes too. The connective tissue between ephemeral AI sessions.

Named after aether -- the substance that fills the space between things.

What it does

  • MCP server -- Claude Code sessions connect and get tools: inbox read/write, persistent memory, cross-session coordination
  • Inbox system -- each session (or user) registers a named inbox. Messages are delivered asynchronously. Sessions poll or subscribe for new messages
  • Persistent state -- survives across sessions. What one session writes, the next one reads

What it doesn't do (yet)

  • Agent Client Protocol -- ACP (Zed/JetBrains) could let kaether drive Claude Code sessions programmatically. Parked for now
  • Multi-user auth -- SSH/GPG key-based message signing for accepting messages from other users. Future addition
  • A2A -- Google/IBM agent-to-agent protocol for cross-framework agent communication. Overkill for now

Architecture

Elixir/OTP supervision tree
├── Phoenix (HTTP layer)
│   ├── MCP endpoint (Streamable HTTP or SSE)
│   └── Inbox API (REST, for non-MCP clients)
├── Inbox.Registry (GenServer per inbox, ETS-backed)
├── Store (SQLite via Exqlite -- messages, state)
└── Future: ACP client, multi-user auth

Kaether is NOT a fork of kbase_bot. Clean start, different concerns. kbase_bot is a Telegram-first personal assistant. Kaether is infrastructure -- a protocol server that other agents connect to.

Prior art

  • ~/code/jairo/jairo-personal/kbase_bot -- Telegram bot with LLM manager, tool registry, task spawning. Shares the Elixir/OTP/SQLite stack but different scope
  • Elixir MCP libraries: hermes_mcp, aide, gen_mcp, phantom_mcp -- pick one for the MCP layer
  • ex_mcp -- implements both MCP and ACP, has Claude Code adapter. Strong candidate if we want ACP later

MCP tools to expose (v0)

The first useful MCP server should give Claude Code sessions:

  • inbox/read -- read messages from my inbox
  • inbox/write -- send a message to a named inbox
  • inbox/list -- list available inboxes
  • state/get, state/set -- persistent key-value store across sessions
  • memo/append, memo/read -- append-only shared scratchpad (like a shared LOG.md)

Tech decisions

  • Elixir + Phoenix -- HTTP layer, channels for future real-time subscriptions
  • SQLite via Exqlite -- embedded, no external deps, proven in kbase_bot
  • MCP library TBD -- evaluate hermes_mcp vs aide vs gen_mcp for server impl
  • No umbrella app -- single mix project, keep it simple until complexity demands otherwise

Relationship to Claude Code agent teams

Claude Code has built-in agent teams (experimental, v2.1.32+). A lead session spawns teammate sessions that communicate via a mailbox, share a task list with claim/dependency semantics, and discover each other through ~/.claude/teams/{name}/config.json.

What agent teams already handle: intra-team messaging (message + broadcast), task coordination with file locking, teammate discovery. Kaether doesn't need to replicate any of this.

What kaether adds on top:

  • Persistence -- agent teams are ephemeral. When the lead cleans up, state is gone. No session resumption for in-process teammates. Kaether provides durable inboxes and state that outlive any single team or session
  • Cross-session coordination -- teams can't talk to other teams. Kaether bridges sessions that don't know about each other
  • External reach -- agent teams are local-only. Kaether's HTTP API opens messaging to non-Claude-Code agents, other users, webhooks, cron jobs
  • Shared scratchpad -- teammates can't see each other's context windows and the built-in task list is the only shared state. Kaether's memo/append gives teams a persistent shared log

Key integration point: MCP servers configured in project settings automatically propagate to all teammates. One .claude/settings.json entry for kaether gives every teammate on every team access to persistent inboxes and state.

Design decisions

  • Transport: HTTP/SSE -- kaether is an always-on server, not a subprocess. If MCP over HTTP/SSE hits friction, ship a thin stdio bridge that proxies to the server
  • Inbox semantics: pull first -- sessions poll for messages. Push (SSE/WebSocket) is a natural future addition via Phoenix Channels
  • Message format: plain text + optional JSON metadata -- keep it simple, structured payloads when needed
  • No umbrella app -- single mix project until complexity demands otherwise
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment