Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.12"]
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
- name: Set up Python
run: uv python install ${{ matrix.python-version }}
- name: Create venv and install
run: |
uv venv .venv --python ${{ matrix.python-version }}
uv pip install --python .venv/bin/python -e .[dev]
- name: Ruff check
run: .venv/bin/ruff check .
- name: Ruff format
run: .venv/bin/ruff format --check .
- name: Mypy
run: .venv/bin/mypy src
- name: Pytest
run: .venv/bin/pytest -q
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.venv/
venv/
data/
*.key
__pycache__/
*.pyc
.pytest_cache/
.mypy_cache/
.ruff_cache/
*.egg-info/
dist/
build/
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Changelog

## 1.0.0

Initial release of the Poke Interconnect Protocol (PIP) v1.

- Core: Ed25519 identity, canonical-JSON signed envelopes, schema-validated
payloads (handshake, message, data, receipt, error), policy engine with
scopes/consent/rate limits, outbound redaction, replay protection,
idempotency, persistent outbox with backoff, in-memory and SQLite stores.
- Transports: FastAPI HTTP (`/.well-known/pip`, `/healthz`, `/pip/v1/inbox`,
`/metrics`, optional bearer gate, `HttpPeerClient`) and MCP via `FastMCP`
(5 tools, 3 resources, 2 prompts, stdio + streamable-http).
- CLI `pip-node`: `keygen`, `identity`, `serve-http`, `serve-mcp`, `sign`,
`send`.
- Config via `PIP_*` environment (pydantic-settings); Prometheus metrics and
JSON structured logs.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 CommunityPoke contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
139 changes: 137 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,137 @@
# hivemind-public
Public Poke Interconnect Protocol (PIP v1) and Poke-compatible MCP server for autonomous communication and data exchange between Poke instances
# poke-interconnect — Poke Interconnect Protocol (PIP) v1

PIP lets independent Poke instances discover each other's capabilities and
exchange **signed, consented, idempotent** messages and data. One envelope
format, one policy engine, and one security model are shared by two
transports: an HTTP/JSON API and an MCP server.

The full normative spec lives in [`docs/PROTOCOL.md`](docs/PROTOCOL.md).
Install name: `poke-interconnect`; import name: `pip_protocol`; CLI: `pip-node`.

## Architecture

```
Peer A Peer B
┌──────────────────┐ ┌──────────────────┐
│ pip-node / SDK │ │ pip-node / SDK │
│ ┌────────────┐ │ │ ┌────────────┐ │
│ │ Node │ │ envelope │ │ Node │ │
│ │ sign/verify│ ├─────────►│ │ verify → │ │
│ │ policy │ │ receipt │ │ policy → │ │
│ │ redaction │ │◄─────────┤ │ handler │ │
│ └─────┬──────┘ │ │ └─────┬──────┘ │
│ HTTP / MCP │ │ HTTP / MCP │
└──────────────────┘ └──────────────────┘
Ed25519 identity · pip:<base32(sha256(pubkey))[:26]>
```

## Quickstart

```bash
uv venv .venv && uv pip install -e .[dev] # or: pip install poke-interconnect

# 1. generate an instance key (mode 0600)
pip-node keygen --out ./data/instance.key

# 2. configure peers and consent
cp config/policy.example.yaml config/policy.yaml # edit peers/scopes/consents
cp config/.env.example .env # optional

# 3. serve
PIP_PRIVATE_KEY_FILE=./data/instance.key PIP_POLICY_FILE=./config/policy.yaml \
pip-node serve-http # HTTP on 127.0.0.1:8642
pip-node serve-mcp --transport stdio # MCP over stdio
pip-node serve-mcp --transport streamable-http # MCP over HTTP
```

Other CLI commands: `pip-node identity`, `pip-node sign`, `pip-node send`
(see `pip-node --help`). Try the in-process demo:
`python examples/two_nodes_demo.py`.

### MCP client config

See `config/mcp-client.example.json`:

```json
{ "mcpServers": { "poke": {
"command": "pip-node",
"args": ["serve-mcp", "--transport", "stdio"],
"env": { "PIP_PRIVATE_KEY_FILE": "./data/instance.key" } } } }
```

## HTTP API (§8)

| Endpoint | Auth | Description |
|---|---|---|
| `GET /.well-known/pip` | none | signed handshake envelope (identity + capabilities) |
| `GET /healthz` | none | `{"status":"ok","version":"pip/1.0"}` |
| `POST /pip/v1/inbox` | signature (+bearer) | envelope → signed `receipt`/`data`/`error` |
| `GET /metrics` | bearer if set | Prometheus text format |

`PIP_HTTP_BEARER_TOKEN` is an optional defense-in-depth gate; it never
replaces the envelope signature. `X-PIP-Request-Id` is echoed/generated,
`X-PIP-Trace-Id` propagated to logs.

## MCP (§9)

Tools: `pip_handshake`, `pip_send_message`, `pip_exchange_data`,
`pip_get_receipt`, `pip_list_capabilities` (public). Every tool takes a signed
`envelope` dict; failures return a **signed `error` envelope** in the result
(never a raw exception).

Resources: `pip://identity`, `pip://capabilities`, `pip://policy/scopes`.
Prompts: `pip_compose_message(to, subject, intent)`,
`pip_request_data(dataset, purpose)`.

## Configuration (§12)

| Variable | Default | Notes |
|---|---|---|
| `PIP_PRIVATE_KEY_FILE` | `./data/instance.key` | mode 0600; key material never in env |
| `PIP_DISPLAY_NAME` | `poke` | |
| `PIP_POLICY_FILE` | `./config/policy.yaml` | YAML or JSON |
| `PIP_STORE_URL` | `memory://` | or `sqlite:///data/pip.db` |
| `PIP_HTTP_HOST` / `PIP_HTTP_PORT` | `127.0.0.1` / `8642` | loopback by default |
| `PIP_PUBLIC_HTTP_URL` / `PIP_PUBLIC_MCP_URL` | unset | advertised endpoints |
| `PIP_HTTP_BEARER_TOKEN` | unset | optional transport gate |
| `PIP_MAX_CLOCK_SKEW_SECONDS` | `300` | |
| `PIP_IDEMPOTENCY_TTL_SECONDS` | `86400` | |
| `PIP_LOG_LEVEL` | `INFO` | JSON structured logs |

## Security model (summary)

Ed25519 signatures over canonical JSON (`"PIPv1\n" + canonical_json(env-sig)`);
peers pinned by public key with rotation grace; nonce + time-window replay
protection; idempotency keys for effectively-once delivery; scope + consent
authorization; outbound redaction before signing; size caps and per-peer token
bucket rate limits. See §13 threat model and `docs/SECURITY.md`.

## Delivery semantics

At-least-once from the sender (client retries with the same
`idempotency_key`), effectively-once at the receiver. `pip.delivery.Outbox`
persists pending envelopes with exponential backoff (base 2s, cap 300s, 8
attempts → dead).

## Observability

Prometheus counters `envelopes_received_total{type,outcome}`,
`envelopes_sent_total`, `policy_denials_total{code}`; histogram
`handler_seconds`. Structured JSON logs, one line per envelope/request — never
payload bodies, keys, or tokens.

## Development

```bash
uv pip install -e .[dev]
ruff check . && ruff format --check . && mypy src && pytest -q
```

## Limitations (v1)

Transport confidentiality is delegated to TLS (terminate at a reverse proxy);
no metadata privacy; no Sybil resistance / peer reputation.

## License

MIT — see `LICENSE`.
15 changes: 15 additions & 0 deletions config/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Example PIP environment configuration (spec §12). No real secrets here.
PIP_PRIVATE_KEY_FILE=./data/instance.key
PIP_DISPLAY_NAME=poke
PIP_POLICY_FILE=./config/policy.yaml
PIP_STORE_URL=memory://
# PIP_STORE_URL=sqlite:///data/pip.db
PIP_HTTP_HOST=127.0.0.1
PIP_HTTP_PORT=8642
# PIP_PUBLIC_HTTP_URL=https://poke.example.com
# PIP_PUBLIC_MCP_URL=
# PIP_HTTP_BEARER_TOKEN=
PIP_MAX_CLOCK_SKEW_SECONDS=300
PIP_IDEMPOTENCY_TTL_SECONDS=86400
PIP_LOG_LEVEL=INFO
# PIP_ALLOW_INSECURE_KEY_PERMS=1 # tests only
18 changes: 18 additions & 0 deletions config/mcp-client.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"mcpServers": {
"poke-stdio": {
"command": "pip-node",
"args": ["serve-mcp", "--transport", "stdio"],
"env": {
"PIP_PRIVATE_KEY_FILE": "./data/instance.key",
"PIP_POLICY_FILE": "./config/policy.yaml",
"PIP_STORE_URL": "memory://",
"PIP_DISPLAY_NAME": "poke"
}
},
"poke-http": {
"url": "http://127.0.0.1:8642/mcp",
"comment": "streamable-http variant: run `pip-node serve-mcp --transport streamable-http` and set Authorization header if PIP_HTTP_BEARER_TOKEN is configured"
}
}
}
21 changes: 21 additions & 0 deletions config/policy.example.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Example PIP policy file (spec §5). Copy to config/policy.yaml and edit.
# Contains NO real keys or secrets.
allow_unknown_peers: false
default_scopes: []
peers:
- instance_id: pip:examplepeer00000000000000
public_key: ed25519:REPLACE_WITH_PEER_PUBLIC_KEY
display_name: "Alice's Poke"
scopes: [messages:send, data:request]
consents:
- resource: data:notes
actions: [read]
expires: 2027-01-01T00:00:00Z
granted_by: operator
rate_limits:
per_peer_per_minute: 60
burst: 20
max_payload_bytes: 262144
redaction:
fields: [email, phone, ssn, api_key, token, password, secret]
patterns: ["\\b[\\w.+-]+@[\\w-]+\\.[\\w.]+\\b"]
Loading
Loading