Metadata-Version: 2.4
Name: skcomms
Version: 0.1.5
Summary: Sovereign communications for AI agents — multi-channel transport + FQID realm addressing
Author-email: smilinTux <chefboyrdave2.1@gmail.com>
License: GPL-3.0-or-later
Project-URL: Homepage, https://smilintux.org
Project-URL: Repository, https://github.com/smilinTux/skcomms
Project-URL: Issues, https://github.com/smilinTux/skcomms/issues
Project-URL: Changelog, https://github.com/smilinTux/skcomms/releases
Keywords: ai,agent,communication,realm,routing,identity,pgp,envelope,sovereign,syncthing,p2p,encrypted,transport,messaging
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Communications
Classifier: Topic :: Security :: Cryptography
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pydantic<3.0,>=2.0
Requires-Dist: psutil>=5.9
Requires-Dist: pyyaml<7.0,>=6.0
Requires-Dist: segno>=1.5
Requires-Dist: cbor2>=5.4
Requires-Dist: dissononce>=0.34.3
Provides-Extra: skcapstone
Requires-Dist: skcapstone>=0.6.8; extra == "skcapstone"
Provides-Extra: cli
Requires-Dist: click>=8.1; extra == "cli"
Requires-Dist: rich>=13.0; extra == "cli"
Provides-Extra: crypto
Requires-Dist: capauth>=0.1.0; extra == "crypto"
Requires-Dist: pgpy>=0.6.0; extra == "crypto"
Provides-Extra: nostr
Requires-Dist: websockets>=12.0; extra == "nostr"
Requires-Dist: cryptography<44.0,>=42.0; extra == "nostr"
Provides-Extra: websocket
Requires-Dist: websockets>=12.0; extra == "websocket"
Provides-Extra: webrtc
Requires-Dist: aiortc>=1.9.0; extra == "webrtc"
Requires-Dist: websockets>=12.0; extra == "webrtc"
Provides-Extra: telegram
Requires-Dist: telethon>=1.36; extra == "telegram"
Provides-Extra: discord
Requires-Dist: discord.py>=2.3; extra == "discord"
Provides-Extra: discovery
Requires-Dist: zeroconf>=0.131.0; extra == "discovery"
Provides-Extra: meshtastic
Requires-Dist: meshtastic>=2.3; extra == "meshtastic"
Provides-Extra: api
Requires-Dist: fastapi<1.0.0,>=0.109.0; extra == "api"
Requires-Dist: uvicorn[standard]>=0.27.0; extra == "api"
Provides-Extra: all
Requires-Dist: click>=8.1; extra == "all"
Requires-Dist: rich>=13.0; extra == "all"
Requires-Dist: psutil>=5.9; extra == "all"
Requires-Dist: capauth>=0.1.0; extra == "all"
Requires-Dist: pgpy>=0.6.0; extra == "all"
Requires-Dist: websockets>=12.0; extra == "all"
Requires-Dist: cryptography<44.0,>=42.0; extra == "all"
Requires-Dist: zeroconf>=0.131.0; extra == "all"
Requires-Dist: fastapi<1.0.0,>=0.109.0; extra == "all"
Requires-Dist: uvicorn[standard]>=0.27.0; extra == "all"
Requires-Dist: aiortc>=1.9.0; extra == "all"
Requires-Dist: telethon>=1.36; extra == "all"
Requires-Dist: discord.py>=2.3; extra == "all"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Requires-Dist: black>=24.0; extra == "dev"
Requires-Dist: ruff>=0.3; extra == "dev"
Requires-Dist: fastapi<1.0.0,>=0.109.0; extra == "dev"
Requires-Dist: httpx>=0.27; extra == "dev"
Requires-Dist: pgpy>=0.6.0; extra == "dev"
Requires-Dist: websockets>=12.0; extra == "dev"
Requires-Dist: cryptography<44.0,>=42.0; extra == "dev"
Dynamic: license-file

# skcomms — Sovereign Realm-Aware Comms Protocol

> **Your agents talk to each other. Cryptographically. No server, no SaaS, no
> central registry.** skcomms defines *what a message is* between sovereign AI
> agents — addressed by a human-readable FQID (`<agent>@<operator>.<realm>`),
> signed with your own PGP key, and routed through a filesystem message tree that
> Syncthing carries between operators. Identity is the fingerprint; the handle is
> just the label.

skcomms is the **Comms protocol capability** of the [SKWorld](https://skworld.io)
sovereign agent ecosystem. It is the *protocol over transport*: the layer that
says who a message is from, who it's for, which realm it belongs to, and that it
hasn't been tampered with. The bytes themselves are carried by
[`skcomms`](https://github.com/smilinTux/skcomms) (singular, transport) — skcomms
(plural) is what those bytes *mean*.

> **Canonical.** The `skcomms → skcomms` pivot is complete. `skcomms` is the in-use
> package; the old `skcomms` is now a thin backward-compat transport shim. Build on
> `skcomms`. The realm layer (FQID addressing, signed Envelope v1, PGP TOFU,
> cross-operator consent) is the canonical surface; the inherited transport
> commands are still present and summarized at the end.

---

## The 60-second version

A message is an **Envelope v1**, addressed by FQID, signed with your PGP key, and
dropped into a filesystem tree that Syncthing replicates to the peer:

```mermaid
flowchart LR
    YOU["you<br/>lumina@chef.skworld"] -->|"skcomms send opus@casey.douno"| ENV["build Envelope v1<br/>(FQID-addressed body)"]
    ENV --> SIGN["sign it<br/>(detached PGP over canonical bytes)"]
    SIGN --> DROP["drop in your outbox<br/>+ the peer's inbox dir"]
    DROP -->|"Syncthing replicates"| PEER["opus reads inbox<br/>verifies signature ✓ / ✗"]
    PEER -->|"unknown / mismatched key"| TOFU["TOFU reject<br/>(first key wins, conflicts refused)"]
```

Nothing phones home. You write to **your own** outbox, you read from **peer**
inboxes, the signature proves authenticity, and the PGP fingerprint — pinned on
first contact (SSH host-key style TOFU) — is the real identity behind the handle.

---

## Why two repos? `skcomms` vs `skcomms`

| Concern | Repo | Layer |
|---|---|---|
| Carrying bytes between operators (Syncthing, IMAP, file, WebRTC, Nostr, …) | [`skcomms`](https://github.com/smilinTux/skcomms) | **Transport** |
| Defining what a message *is* — envelope schema, FQID identity, signing, realm routing, consent | `skcomms` (this repo) | **Protocol** |

Split on 2026-04-26 so each layer moves at its own cadence and the dependency
graph stays acyclic (`skcomms → skcomms`, never the reverse). The realm protocol
fixes the "two `jarvis`'s on the same realm" collision problem — disambiguation by
PGP fingerprint, not by name.

---

## Quickstart

```bash
# Lives in the shared SK* venv:
~/.skenv/bin/pip install -e ".[cli,crypto]"

skcomms init                                   # scaffold the ~/.skcomms realm tree
skcomms send opus@casey.douno "sync complete"  # build + sign + drop an Envelope v1
skcomms inbox                                  # list + verify signed messages (✓/✗)
skcomms peers add opus@casey.douno \           # pin a peer (Syncthing id + PGP TOFU)
    --syncthing-device-id ABCDEF1-...-2345678 --pubkey ./opus.asc
skcomms registry resolve opus@casey.douno      # find a peer's connectivity hints
```

`realm` and `operator` come from `~/.skcapstone/cluster.json`; the `agent`
component is the resolved self identity (via capauth). All paths honor the
`SKCOMMS_HOME` env override (default `~/.skcomms`). The CLI entrypoint is
`skcomms`; see `skcomms --help`.

---

## What skcomms provides

| Piece | What it is | Module |
|---|---|---|
| **FQID identity** | three-tier `<agent>@<operator>.<realm>`; resolution delegates to capauth | `identity.py`, `cluster.py` |
| **Envelope v1** | the canonical FQID-addressed message schema + stable `canonical_bytes()` for signing | `envelope.py` |
| **Signing & verify** | detached PGP signature over canonical bytes; SHA-256 tamper pre-check | `signing.py` |
| **Realm tree** | `~/.skcomms/<realm>/<operator>/<agent>/{outbox,inbox}` + `.stignore`; idempotent scaffold | `home.py` |
| **Mailbox** | send (build → sign → drop) and inbox (read → verify) over the tree | `mailbox.py` |
| **TOFU trust** | first PGP fingerprint per FQID wins; a conflicting key is refused, never silently rebound | `tofu.py` |
| **Peer wiring** | bind FQID → Syncthing device id + PGP fingerprint in `peers.json` (pure-pgpy, no keyring) | `peers.py` |
| **Realm registry** | multi-backend FQID resolver — sovereign Syncthing-shared file (default) + opt-in HTTPS + Tailscale | `registry.py` |
| **Consent grants** | PGP-signed cross-operator memory-recall tokens that skmemory verifies offline | `grants.py` |
| **skcapstone adapter** | default-on-by-presence: route alerts via `sk-alert`, register health sweep with `skscheduler` | `integration.py` |
| **Transport layer** | inherited skcomms stack (Syncthing/file/Nostr/WebSocket/WebRTC/Tailscale, router, daemon, REST) | `core.py`, `transports/`, `router.py`, … |

---

## Where it lives in SKStack v2

skcomms is a **Comms** capability. It sits *above* the transport layer (`skcomms`),
reads identity from **Core** (capauth, cluster.json), and feeds the message
schema consumed by `skchat`. Its only hard dependencies are PGP (`pgpy`) and the
filesystem; everything else — Syncthing transport, the skcapstone bus, the
scheduler — is reused when present and degrades gracefully when not.

```mermaid
flowchart TD
    subgraph COMMS["Comms"]
      SKCOMMS["**skcomms**<br/>Envelope v1 · FQID routing · PGP signing · TOFU · consent"]
      SKCOMMS["skcomms<br/>(transport: Syncthing · file · WebRTC · Nostr …)"]
      SKCHAT["skchat<br/>(chat app over the protocol)"]
    end
    subgraph CORE["Core (identity & trust)"]
      CAPAUTH["capauth<br/>(FQID + PGP key resolver)"]
      SKMEMORY["skmemory<br/>(consumes consent grants)"]
    end
    subgraph PLATFORM["Platform services (reused when present)"]
      ALERT["sk-alert bus"]
      SCHED["skscheduler"]
      CAP["skcapstone<br/>(cluster.json · agent key store)"]
    end

    CAPAUTH -->|"resolves FQID + signing key"| SKCOMMS
    CAP -->|"realm / operator (cluster.json)"| SKCOMMS
    SKCOMMS -->|"signed Envelope v1"| SKCHAT
    SKCOMMS -->|"drops into realm tree, replicated by"| SKCOMMS
    SKCOMMS -->|"recall-consent tokens"| SKMEMORY
    SKCOMMS -. "alerts (optional)" .-> ALERT
    SKCOMMS -. "health sweep (optional)" .-> SCHED
```

See **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)** for the full message
lifecycle, the trust model, the registry resolution flow, and the source map.

---

## Documentation

| Doc | Contents |
|---|---|
| **[Architecture](docs/ARCHITECTURE.md)** | message lifecycle, TOFU trust model, registry resolution, consent grants, integration modes, source map (mermaids) |
| **[Pairing](docs/PAIRING.md)** | end-to-end walkthrough: pair with another operator |
| **[Syncthing topology](docs/SYNCTHING_TOPOLOGY.md)** | how the realm tree replicates over Syncthing (folders, directionality, ignore patterns) |

---

## CLI reference

The realm layer is the canonical surface. The older transport-config commands are
the inherited skcomms layer and are summarized at the end.

### Bootstrap

| Command | What it does |
|---|---|
| `skcomms init [--agent <name>]` | Scaffold `~/.skcomms/<realm>/<operator>/<agent>/{outbox,inbox}` (from `cluster.json` + resolved identity) plus a top-level `.stignore`. Idempotent — never clobbers existing messages. |
| `skcomms init-config [--name … --fingerprint … -f]` | **Legacy.** Write the old transport config `~/.skcomms/config.yml` (auto-detect Syncthing, test the file transport). Not the realm tree. |

### Realm messaging

| Command | What it does |
|---|---|
| `skcomms send <to_fqid> <message> [-a <agent>] [-s <subject>] [-t <thread>] [--reply-to <id>]` | Build a signed **Envelope v1** from the resolved identity, sign it (detached PGP), and write it to the sender's `outbox` *and* the recipient peer's `inbox` under `~/.skcomms`. |
| `skcomms inbox [-a <agent>] [--json-out]` | List + **verify** signed messages in this agent's inbox. Each `SignedEnvelope` is parsed and its signature checked against the sender's known public key; `✓` / `✗` shown per message. |

`<to_fqid>` / `<from_fqid>` are FQIDs of the form `<agent>@<operator>.<realm>`
(e.g. `opus@casey.douno`).

### Peers & registry

| Command | What it does |
|---|---|
| `skcomms peers [-a <agent>] [--json-out]` | List known peers in the realm tree — every `<realm>/<operator>/<agent>` dir other than this agent's, with its inbox count. |
| `skcomms peers add <fqid> --syncthing-device-id <id> --pubkey <path> [--via-registry] [--tailscale <node>]` | Wire a peer's Syncthing device id + PGP key into `peers.json`. Derives the fingerprint from `--pubkey` and **TOFU-binds** `fqid → fingerprint` (a conflicting key on re-add is refused). `--via-registry` resolves device id + pubkey from the realm registry; `--tailscale` records a connectivity hint. |
| `skcomms peers show <fqid> [--json-out]` | Show a peer's stored connectivity record. |
| `skcomms registry list [--json-out]` | List every peer the enabled registry backends know about, with hint types + source backends. |
| `skcomms registry resolve <fqid> [--json-out]` | Resolve a single fqid across backends and merge their hints. |

The registry consults pluggable backends — the **sovereign Syncthing-shared file**
(`_realm/peers.json`) is the default, with **opt-in HTTPS** and **Tailscale**
layered on top.

### Consent grants

Cross-operator memory-recall consent. A grant is a PGP-signed token that lets a
remote agent read one of this operator's memory collections across an
operator/realm boundary; the consumer (skmemory) verifies it offline.

| Command | What it does |
|---|---|
| `skcomms grant collection-read --collection <op>.<realm>/<name> --to <peer-fqid> [--expires 30d] [-o <file>]` | Mint a signed read-consent token. `--expires` accepts `<N>d` (default `30d`) or an ISO-8601 date. |
| `skcomms grants accept <source>` | Verify + accept a peer's token (file path or `-` for stdin). Signature, granter TOFU, and expiry are checked, then it's merged idempotently into `recall_collections_consent.json` — the file skmemory reads. |
| `skcomms grants list [--json-out]` | List the consent tokens currently held. |

### Legacy transport layer (inherited skcomms)

These predate the realm layer and operate on `~/.skcomms/` (transport config / peer
store) rather than the `~/.skcomms/` realm tree:

- `skcomms send-transport <recipient> <message>` — route through all configured
  transports (failover / broadcast / stealth / speed).
- `skcomms receive` / `skcomms daemon` — poll all transports (one-shot / continuous).
- `skcomms peers-transport`, `skcomms peer {add,remove,list,fetch,export,import}`,
  `skcomms discover` — the transport peer store + DID key exchange.
- `skcomms serve`, `skcomms stats`, `skcomms status`, `skcomms heartbeat …`,
  `skcomms queue …`, `skcomms pubsub …`, `skcomms skill …` — REST API, metrics,
  node-health beacons, dead-letter queue, pub/sub, skill marketplace.

Prefer the realm-layer commands above for new work.

---

## Integration modes

skcomms runs fully standalone and adopts skcapstone services *by presence*:

| Mode | Trigger | Alert path | Scheduler |
|---|---|---|---|
| **Standalone** | `skcapstone` not installed | native `logging` | native heartbeat daemon / systemd `skcomms.service` |
| **Integrated** | `skcapstone` installed (default-on) | `sdk.alert()` → topic `skcomms.<severity>` → Telegram/notify | `sdk.register_job()` → fleet `skscheduler` drop-in `skcomms_health_sweep` |
| **Forced standalone** | `SK_STANDALONE=1` | native `logging` | native |

Enable with `pip install skcomms[skcapstone]` — no config change needed; package
presence is the signal. Alert topics follow `skcomms.<severity>`; the semantic
event name lives in the payload `event` field so `skcapstone alerts` routes by
severity.

---

## License

**GPL-3.0-or-later** — see [`LICENSE`](LICENSE). Matches the rest of the smilinTux
ecosystem (including `skcomms`, the transport library this depends on). Sovereign-AI
infra ships under copyleft so downstream forks stay open.

---

Part of the **[SKWorld](https://skworld.io)** sovereign ecosystem · site:
**[skcomms.skworld.io](https://skcomms.skworld.io)** · 🐧 smilinTux
