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
47 changes: 6 additions & 41 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,9 @@
# TPS Architecture

TPS is designed to securely manage a fleet of AI agents distributed across multiple "Branch Offices" (remote VMs or local Docker sandboxes).
Moved to **[docs/architecture.md](docs/architecture.md)** — the Agent OS kernel /
layered model (Trust Model, Mail Bridge, isolation strategies, state persistence) is the
single authority for TPS's architecture.

## Topology: Hub and Spoke

TPS uses a strict hub-and-spoke topology.
- The **Host** (e.g., your laptop) is the hub.
- The **Branch Offices** (e.g., cloud VMs) are the spokes.
- Branches **do not** talk directly to each other. All cross-branch communication routes through the Host's mail relay.
- The Host initiates connections to the Branches. This solves NAT traversal issues since the Host is often behind NAT, while Branch VMs usually have public IPs.

## Wire Protocol: Noise_IK over WebSocket

To securely bridge the Host and Branch Offices, TPS uses the **Noise Protocol Framework** (specifically `Noise_IK`).

1. **Transport**: WebSockets (`wss://`). This allows traffic to pass seamlessly through HTTP proxies and TLS terminators (like `exe.dev` or `ngrok`), avoiding the need for raw TCP ports.
2. **Authentication**: Mutual authentication using static Ed25519 keypairs. The `_IK` handshake allows the Host to send encrypted payload data in the very first flight (0-RTT for known peers).
3. **Key Custody**: The Host's private key NEVER leaves the Host. Branch Offices generate their own keys during `tps branch init`.

## The Three-Channel Model

Agents in TPS do not dump code, text, and data into a single massive LLM context window. Communication is strictly segregated:

1. **Mail**: For control plane messages, status updates, commands, and webhook events. Sent as JSON envelopes, Ed25519-signed, and delivered via the Noise_IK transport.
2. **Git**: For artifacts. Code, specs, and documentation are committed and pushed. Agents pull the repo to see the state of the world.
3. **APIs**: For external data (e.g., web searches, tool use).

## Mail Handlers & Manifests (`tps.yaml`)

Each agent directory contains a `tps.yaml` manifest.

The Branch Daemon discovers these manifests and wires up a **mail handler pipeline**. When mail arrives, the daemon checks the sender and body against the handler's `match` rules. If matched, the daemon executes the handler script, providing the mail content via `stdin` and metadata via environment variables.

Handlers can return plain text (to reply) or JSON envelopes (`{"action": "forward", "to": "other-agent"}`) to route messages locally within the branch.

## Sandboxing and Process Isolation

![Milton's Sandbox](docs/media/miltons-sandbox.png)

TPS provides multiple layers of isolation for agents, ensuring they only access what they need:
- **Docker Sandboxes**: For local agents, TPS provisions secure Docker microVMs (Branch Offices) that have no direct host filesystem access.
- **Remote VMs**: For true physical isolation, Branch Offices can be deployed on remote cloud VMs.
- **`nono` System Call Filtering**: Host agents execute commands under `nono` profiles, providing read-only boundaries and strict system call filtering to prevent root pollution or unauthorized network egress.
For the wire-protocol detail of remote Branch Offices specifically (Noise_IK handshake,
SSH-tunnel transport, provisioning), see [docs/branch-office.md](docs/branch-office.md).
For design rationale — *why* TPS is built this way — see [DESIGN.md](DESIGN.md).
8 changes: 4 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ TPS is built with TypeScript and [Bun](https://bun.sh/).

```bash
# Clone the repository
git clone https://github.com/tpsdev-ai/tps.git
cd tps
git clone https://github.com/tpsdev-ai/cli.git
cd cli

# Install dependencies
bun install
Expand All @@ -23,9 +23,9 @@ bun test

## Architecture Notes

Before contributing, please read the [ARCHITECTURE.md](ARCHITECTURE.md). TPS uses a strict hub-and-spoke topology and isolates communication across three channels (Mail, Git, APIs).
Before contributing, please read [docs/architecture.md](docs/architecture.md) (the system model) and [DESIGN.md](DESIGN.md) (why it's built this way).

When modifying the branch daemon or transport layers, keep the following security boundaries in mind:
When modifying the branch daemon or transport layers, keep the following security boundaries in mind — see [DESIGN.md § Trust boundaries](DESIGN.md#trust-boundaries) for the rationale:
- **Never expose the Host's private key**.
- **Always validate inputs** on cross-boundary messaging.
- **Fail closed** on authentication or permission errors.
Expand Down
97 changes: 97 additions & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# TPS — Design Invariants

This is TPS's design DNA: the invariants that decide what we build and, just as
importantly, what we refuse to build — not a feature list (see [README.md](README.md))
and not an architecture reference (see [docs/architecture.md](docs/architecture.md)). If
you're adopting TPS, extending it, or wondering why it works a certain way, this is the
page that answers "why," not "what."

TPS exists to let a fleet of agents — on your workstation, in a Docker sandbox, on a
remote VM — coordinate without stepping on each other or on you. Every invariant below is
in service of that: agents you can trust because their actions are provably theirs,
infrastructure that recovers instead of quietly rotting, and a unit of work small enough
to review honestly.

## Signed everything

Every agent identity in TPS is an Ed25519 keypair, generated on the machine it belongs
to and never exported. Mail envelopes are signed. Branch Office pairing is a mutual
Noise_IK handshake between static keys, not a shared secret or a bearer token. When a
Branch relays mail back through the Host, the relay overwrites the `from` field with the
verified sender identity rather than trusting whatever the message claims — a compromised
Branch can flood or misbehave, but it cannot forge who it is.

This is the same principle applied consistently everywhere in the system: trust flows
from **verified provenance** — a signature that checks out, a handshake that completed —
never from "who claims what." An agent proves itself cryptographically or it doesn't get
to act as itself. There is no middle tier of "probably fine, no key required."

## Self-healing over keepalive

A stuck mail consumer, a stale watcher, a failed delivery — these are expected failure
modes in a system with this many moving daemons, not exceptions. The invariant is that
the system detects and recovers from them **itself**, and does so loudly: an alert plus a
recurrence count, so a restart never quietly papers over a root cause that's getting
worse each time it fires.

A `KeepAlive: true` plist entry that respawns a crashed process is not the same thing as
self-healing — it hides the failure instead of surfacing it. The bar is higher: watchers
that notice their own staleness and kick themselves, daemons that drain a backlog on
reconnect instead of losing it, connection state that's inspectable so "is this actually
healthy" is a `status` command, not a guess. A fix that needs a human to notice something
is broken before it can be applied isn't done — it's deferred, and deferred failures in
an agent fleet compound.

## Idempotent + verifiable

Every action in TPS is safe to retry. Mail delivery is deduplicated and replay-safe —
messages carry an issuing UUID and timestamp, subscribers catch up via cursor-based
replay, and re-running a drain doesn't double-deliver. Nothing in the critical path
assumes it will only ever run once.

Retry-safety only matters if you can tell whether the retry — or the original action —
actually worked, and that confirmation has to come from a channel that can't lie. A
GitHub PR's review state, a `tps office status` health probe, an actual served response
— these are ground truth. An agent's own "done" is not: agents self-report success on
work that's stubbed, partial, or silently failed, and a design that takes a self-report
at face value is building on sand. Verification through an independent channel is not an
optional nicety layered on top of the CLI — it's the property that makes autonomy safe to
extend in the first place.

## The dispatch → review → merge loop is the unit of work

Work moves through TPS as: one task, dispatched with everything the recipient needs to
execute it (a spec, a branch, an output contract) — never a vague pointer requiring
back-and-forth to clarify. The agent does the work and opens a PR; it does not merge its
own change. An independent review — architecture on one lane, security on another — is
the gate, not a courtesy step. Only a change that clears CI and clears review merges.

This loop is deliberately small and deliberately serial: one task in flight per agent,
not a queue of half-started work. The unit of work is the whole loop, not the code —
a task without its review isn't finished, it's paused mid-flight, and pretending
otherwise is how unreviewed shortcuts accumulate in a fleet no single human is reading
line-by-line.

## Trust boundaries

TPS draws one hard line: the **Host** (where your identity, mail, and context live) and
everything else. A Branch Office — Docker sandbox or remote VM — is untrusted by design,
even though it's running your own agent. Three rules enforce that boundary, and they are
non-negotiable regardless of what's convenient at the call site:

- **Never expose the Host's private key.** It is generated once, lives only on the Host,
and every cross-boundary interaction is a signature or a handshake, never a
key handoff.
- **Always validate input at the boundary.** Every message crossing from a Branch into
the Host's mail relay passes a validation gate — identity is overwritten to the
verified sender, origin is stamped, size and quota are enforced — before it's ever
written to a recipient's inbox. Nothing from an untrusted side is trusted just because
it parsed as JSON.
- **Fail closed.** An authentication failure, a permission error, a malformed
cross-boundary message — any of these abort the operation. TPS never falls back to an
unauthenticated or partially-validated path to keep something moving; a blocked
action is recoverable, a silently-downgraded trust boundary is not.

A Branch has no direct access to the Host's filesystem, its `~/.tps/context`, or another
agent's mail — the Mail Bridge is the only door, and it's the door these three rules
guard.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Most agent frameworks assume all agents run in the same memory space. TPS assume

> "I have eight different bosses right now. So that means that when I make a mistake, I have eight different people coming by to tell me about it." — Make your agents communicate through a single, auditable mail interface instead.

See **[DESIGN.md](DESIGN.md)** for the invariants behind that — why identity is signed, why failures self-heal instead of hiding, and why every task moves through review before it merges.

### What You Get

- **Identity & Keys** — Ed25519 keypairs per agent. Agents prove who they are cryptographically, not by env var.
Expand Down
29 changes: 29 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,33 @@ It uses a layered design: **TPS Reports** define the agent's identity and capabi

---

## 0. Communication Channels

Agents in TPS do not dump code, text, and data into a single massive LLM context window.
Communication is strictly segregated across three channels, each suited to a different
kind of payload:

1. **Mail** — control plane messages, status updates, commands, and webhook events. Sent
as Ed25519-signed JSON envelopes (see [§2 The Mail Bridge](#2-the-mail-bridge-ipc) for
the trust boundary they cross, and [docs/branch-office.md](branch-office.md) for the
Noise_IK wire transport used between Host and a remote Branch).
2. **Git** — artifacts. Code, specs, and documentation are committed and pushed. Agents
pull the repo to see the state of the world rather than holding it in-context.
3. **APIs** — external data (web searches, tool use, third-party services).

### Mail Handlers & Manifests (`tps.yaml`)

Each agent directory carries a `tps.yaml` manifest — this is the "TPS Report" in the
System Layers diagram above (the Definition Layer). The Branch Daemon discovers these
manifests and wires up a **mail handler pipeline**: when mail arrives, the daemon checks
the sender and body against the handler's `match` rules, and on a match executes the
handler script, passing the mail body on `stdin` and metadata via environment variables.
A handler can return plain text (sent back as a reply) or a JSON envelope
(`{"action": "forward", "to": "other-agent"}`) to route the message on within the branch
without a round-trip to the Host.

---

## 1. The Trust Model

TPS operates on a **Host/Branch** security model.
Expand Down Expand Up @@ -71,6 +98,8 @@ This ensures that even a compromised Branch Agent cannot forge its identity or f

## 3. Isolation Strategies

![Milton's Sandbox](media/miltons-sandbox.png)

TPS employs "Defense in Depth" using two different isolation technologies.

### Host Isolation: `nono`
Expand Down
Loading