Classification: Public Specification Status: Draft Last Updated: January 2026 Authors: Flywheel Core Team
- Executive Summary
- Problem Statement
- Design Principles
- System Architecture
- Security Model
- Zone Architecture
- Provenance and Taint Tracking
- Capability System
- Wire Protocol
- Connector Manifest
- Automation Recipes
- Registry and Supply Chain
- Lifecycle Management
- Agent Integration
- Observability and Audit
- Error Taxonomy
- Migration Strategy
- Appendices
The Flywheel Connector Protocol (FCP) defines a secure, modular, high-performance framework for integrating external services into the Agent Flywheel ecosystem. FCP enables AI coding agents to safely interact with messaging platforms, cloud services, productivity tools, and other external systems while maintaining strict security boundaries.
If a public-facing integration (Discord, webhooks, social DMs) and a private integration (email, calendar, files) share a process or trust domain, a single prompt injection or parsing bug can lead to catastrophic cross-domain access.
FCP prevents this by enforcing mechanical isolation and topological security:
- Connectors are isolated binaries — No shared memory; sandboxed execution
- Zones define trust topology — Where things may run and how data may flow
- Capabilities define permissions — What a connector or agent may do
- Provenance tracks causal origin — Where an instruction came from
- Approvals gate high-risk actions — Humans or policy must explicitly elevate
- Everything is self-describing — Manifests, schemas, AI hints, recovery maps
| Innovation | Description |
|---|---|
| Zone-Based Security | Mechanical, protocol-level isolation between trust domains |
| Provenance/Taint Tracking | Prevents "untrusted input → privileged action" chains |
| Capability-Based Access | Fine-grained permissions enforced at the binary level |
| Self-Contained Binaries | Single-binary connectors with embedded manifests |
| Cryptographic Verification | Ed25519-signed connectors with reproducible builds |
| Automation Recipes | First-class setup/teardown workflows |
| Agent-Native Design | Purpose-built for AI agent ergonomics and introspection |
FCP explicitly does NOT:
- Support interpreted language runtimes (Python venvs, Node.js, etc.)
- Allow dynamic code execution within connectors
- Use "prompt security" as a primary security mechanism
- Require Docker as the unit of distribution
- Support connectors requiring root/administrator privileges for normal operation
| Term | Definition |
|---|---|
| Connector | A self-contained binary implementing the FCP interface |
| Connector Instance | A running connector process bound to exactly one zone |
| Zone | A security boundary defining trust level and capabilities |
| Capability | A specific permission granted to a connector |
| Manifest | Embedded metadata describing connector properties |
| Hub/Gateway | The Flywheel component orchestrating connectors |
| Principal | An identity (user, agent, or service) making requests |
| Ingress | External input entering the Hub (messages, webhooks, polls) |
| Egress | Outbound action from the Hub/connector to an external service |
| Operation | A named connector function (e.g., gmail.search) |
| Resource URI | Canonical identifier for connector resources (e.g., fcp://...) |
| Cursor | Opaque position token for event stream replay/resume |
| Provenance | Metadata describing the causal origin of a request |
| Taint | A label indicating origin from a less-trusted zone |
| Elevation | Explicit approval to cross trust boundaries |
The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY in this document are to be interpreted as described in RFC 2119.
The core failure mode looks like:
Public Input (Discord) ──► Agent Context ──► Gmail Capability (Catastrophe)
Even if each tool is "permissioned," the agent reasoning substrate can accidentally route untrusted instructions into privileged tools. This can happen via:
- Prompt injection
- Social engineering
- Parsing ambiguity
- Bugs in tool routers
- Model misbehavior under pressure
"Don't read private emails" is trivially bypassed. Security must be mechanical—enforced by the protocol, type system, and binary boundaries—not rhetorical.
| Goal | Description |
|---|---|
| Mechanical Isolation | Connectors MUST be isolated processes with sandboxing |
| Topology Over Discretion | Zones MUST define allowed data/control flows |
| Least Privilege by Construction | Undeclared capabilities MUST NOT be usable |
| Provenance-Aware Safety | Requests MUST carry causal origin; tainted → privileged MUST be blocked |
| Agent-Native Ergonomics | Connectors MUST be introspectable with actionable recovery hints |
| Operational Excellence | Standard metrics, logging, tracing, audit, retries, circuit breakers |
| Ecosystem Scalability | Registry with signatures, attestations, SBOM, conformance tests |
FCP assumes adversarial inputs. Prompting alone MUST NOT be considered a security boundary. All security enforcement happens at the protocol level through:
- Type System: Rust's ownership model prevents capability leaks
- Binary Boundaries: Connectors cannot share memory
- Protocol Validation: All messages validated against schemas
- Cryptographic Attestation: Capabilities bound to signed tokens
Capabilities answer: "What can you do?" Zones answer: "Where are you allowed to do it, and where can data go?"
Both are required. A capability without zone authorization is useless.
The Hub MUST track the causal origin of actions and enforce rules like:
- "Untrusted origin cannot cause privileged writes without explicit elevation."
We prefer:
- Standard process boundaries
- Explicit schemas
- Debuggable transports
...and add performance optimizations (binary encoding, zero-copy) behind stable contracts.
High-risk capabilities MUST be gated by either:
- Explicit policy allowlists
- Explicit human approval (interactive)
- Time-bounded elevation tokens
| Metric | Target | Enforcement |
|---|---|---|
| Cold start | < 50ms | Binary preloading |
| Message latency | < 1ms | Zero-copy IPC |
| Memory overhead | < 10MB | Static allocation |
| CPU overhead | < 1% idle | Event-driven architecture |
| Platform | Architecture | Status |
|---|---|---|
| Linux | x86_64, aarch64 | Tier 1 |
| macOS | x86_64, aarch64 | Tier 1 |
| Windows | x86_64 | Tier 2 |
| FreeBSD | x86_64 | Tier 3 |
┌─────────────────────────────────────┐
│ HUB │
│ ┌───────────────────────────────┐ │
│ │ Policy Engine | Audit Log │ │
│ │ Zone Enforcer | Cap Minter │ │
│ │ Provenance Tracker | Router │ │
│ └───────────────────────────────┘ │
└──────────┬─────────────┬────────────┘
│ │
(isolated) │ │ (isolated)
│ │
┌────────────────▼───┐ ┌───▼────────────────┐
│ Connector A │ │ Connector B │
│ (z:community) │ │ (z:private) │
│ Discord Bot │ │ Gmail/Calendar │
└────────┬───────────┘ └───────────┬────────┘
│ │
External Service External Service
Hub (FCP Host / Gateway) — A long-running daemon that acts as:
| Role | Responsibility |
|---|---|
| Shepherd | Spawns and supervises connectors (health checks, restarts, throttling) |
| Airlock | Routes all messages; enforces validation, zones, capabilities, provenance |
| Keymaster | Issues capability tokens; enforces approval requirements |
| Auditor | Records immutable audit events for sensitive operations |
Connectors — Isolated binaries implementing the FCP interface:
- Provide operations and events
- Maintain optional local state (encrypted, scoped)
- MUST NOT directly communicate with other connectors except through the Hub
Registry — Discovery, distribution, and provenance:
- Git-indexed manifests + signatures (transparent, reviewable)
- HTTP/CDN binaries (fast)
- Attestations + SBOM for supply-chain safety
Vault / Secret Store — Stores secrets, supports rotation, injects ephemeral credentials at runtime. The Hub process MUST delegate secret custody here (or connector-local encrypted storage) and MUST NOT persist raw secrets in its own config or database
Approval Broker — Human/policy approval for high-risk actions (UI, CLI, or policy engine)
FCP MUST support:
- Local single-user: Hub on workstation, connectors local
- Server multi-project: Hub in controlled environment; connectors per project/tenant
FCP MAY support:
- Remote connectors: Over TCP + mTLS for secure cross-host execution
FCP recognizes a small set of data-flow archetypes. Connectors MAY declare one or more archetypes in their manifest; the Hub SHOULD use this to set sensible defaults (buffer sizes, backpressure, polling cadence).
| Archetype | Pattern | Examples | Default Expectations |
|---|---|---|---|
| Request-Response | Agent → Service → Agent | REST, GraphQL, gRPC unary | Idempotency keys for retries |
| Streaming | Service → Agent | WebSocket, SSE, log tail | subscribe + replay/resume |
| Bidirectional | Agent ↔ Service | Chat, collaborative tools | Ack + backpressure |
| Polling | Agent → Service (periodic) | IMAP, RSS, status checks | Explicit poll interval |
| Webhook | Service → Agent (push) | GitHub, Stripe | Signature verification |
| Queue/Pub-Sub | Agent ↔ Broker | Redis, NATS, Kafka | Cursor + consumer groups |
| File/Blob | Agent → Storage | S3, GCS, local FS | Resource URI scoping |
| Database | Agent → DB | Postgres, vector DBs | Query constraints + timeouts |
| CLI/Process | Agent → spawn | git, kubectl, terraform | Command normalization + guardrails |
| Browser | Agent → CDP → Browser | Automation, scraping | Session isolation + screenshots |
FCP is intentionally layered to keep distribution, runtime, and policy concerns separable:
- FCP-Pack (Packaging & Trust) — Manifests, signatures, SBOM, and registry metadata
- FCP-Core (Runtime Protocol) — Handshake, invoke, events, health, shutdown
- FCP-Policy (Zones & Capabilities) — Zone definitions, provenance/taint, approvals, and constraints
FCP assumes the following adversaries:
| Threat | Mitigation |
|---|---|
| Prompt injection causing privileged tool use | Zones + provenance (taint) + approval gates |
| Malicious connector binary | Signatures + attestations + sandbox + least privilege |
| Compromised external service | Schema validation + taint propagation + output constraints |
| Data exfiltration | Zone egress rules + network allowlists + audit |
| Supply chain attacks | SBOM + reproducible builds + provenance chain |
| Abuse / DoS | Rate limits + circuit breakers + resource quotas |
| Accidental privilege mixing | Default deny + explicit zone configuration |
These are hard requirements that FCP enforces mechanically:
- Single-Zone Binding: A connector instance MUST bind to exactly one zone for its lifetime
- Default Deny: If a capability is not explicitly granted to a zone, it MUST be impossible to invoke
- No Cross-Connector Calling: Connectors MUST NOT call other connectors directly; all composition happens through the Hub
- No Credential Custody by Hub Process: The Hub process MUST NOT persist raw OAuth tokens, API keys, or provider secrets. Secrets MUST be stored in a dedicated secret store or connector-local encrypted storage, and only ephemeral credentials may be injected at runtime
- Strong Provenance on Ingress: Every ingress event MUST include principal identity and origin attributes
- Auditable Everything: Every operation invocation MUST produce auditable records with correlation IDs
The Hub MUST guarantee:
- No shared memory between connectors
- Connector filesystem access scoped to connector-owned directories
- Network access is default-deny and capability-driven
- Secrets are never written to logs or config files
Stronger isolation SHOULD be used when available:
| Platform | Mechanisms |
|---|---|
| Linux | namespaces + seccomp + landlock + cgroups |
| macOS | sandbox profiles (seatbelt) + hardened runtime |
| Windows | job objects + restricted tokens + AppContainer |
OWNER (Root Trust)
│
├── ADMINISTRATOR
│ │
│ ├── SERVICE_ACCOUNT
│ │ │
│ │ └── CONNECTOR
│ │
│ └── AGENT
│ │
│ └── SUB_AGENT
│
└── EXTERNAL_USER
│
├── AUTHENTICATED_USER
│
└── ANONYMOUS_USER
┌────────────────────────────────────────────────────────────────┐
│ HOST SYSTEM │
├────────────────────────────────────────────────────────────────┤
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ HUB PROCESS │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Supervisor │ │ IPC Hub │ │ Capability │ │ │
│ │ │ │ │ │ │ Verifier │ │ │
│ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │
│ └─────────┼────────────────┼────────────────┼──────────────┘ │
│ │ │ │ │
│ ┌─────────▼────────────────▼────────────────▼──────────────┐ │
│ │ SANDBOX BOUNDARY │ │
│ │ ┌────────────────────────────────────────────────────┐ │ │
│ │ │ CONNECTOR SANDBOX │ │ │
│ │ │ ┌──────────────────────────────────────────────┐ │ │ │
│ │ │ │ Filesystem: /fcp/<connector>/ │ │ │ │
│ │ │ │ ├── config/ (read-only) │ │ │ │
│ │ │ │ ├── data/ (read-write, encrypted) │ │ │ │
│ │ │ │ ├── cache/ (read-write, ephemeral) │ │ │ │
│ │ │ │ └── tmp/ (read-write, tmpfs) │ │ │ │
│ │ │ └──────────────────────────────────────────────┘ │ │ │
│ │ │ ┌──────────────────────────────────────────────┐ │ │ │
│ │ │ │ Network: Filtered by capability │ │ │ │
│ │ │ │ - Allowed: api.telegram.org:443 │ │ │ │
│ │ │ │ - Denied: * (default) │ │ │ │
│ │ │ └──────────────────────────────────────────────┘ │ │ │
│ │ │ ┌──────────────────────────────────────────────┐ │ │ │
│ │ │ │ IPC: Unix socket only │ │ │ │
│ │ │ │ /fcp/ipc/<connector>.sock │ │ │ │
│ │ │ └──────────────────────────────────────────────┘ │ │ │
│ │ └────────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────┘
A Zone is a security boundary that defines:
- What principals can access resources within it
- What capabilities are available within it
- What connectors can operate within it
- How data flows in and out of it
Zones are capability universes. If the Gmail-read capability does not exist in a zone, it cannot be invoked—no matter what an agent says, no matter what prompt injection occurs.
| Zone ID | Trust Level | Description |
|---|---|---|
z:owner |
100 | Root trust / owner console |
z:private |
90 | Personal email, calendar, files |
z:work |
70 | Work services, internal systems |
z:project:<name> |
60 | Per-project isolation |
z:community |
30 | Semi-trusted communities (Discord servers) |
z:public |
10 | Public/untrusted inputs |
┌─────────────────────────────────────────────────────────────────┐
│ ZONE HIERARCHY │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ OWNER ZONE (z:owner) │ │
│ │ Trust: 100 | Full access to everything │ │
│ │ ┌───────────────────────────────────────────────────┐ │ │
│ │ │ PRIVATE ZONE (z:private) │ │ │
│ │ │ Trust: 90 | Personal data, email, calendar │ │ │
│ │ │ ┌─────────────────────────────────────────────┐ │ │ │
│ │ │ │ WORK ZONE (z:work) │ │ │ │
│ │ │ │ Trust: 70 | Work-related services │ │ │ │
│ │ │ │ ┌───────────────────────────────────────┐ │ │ │ │
│ │ │ │ │ PROJECT ZONE (z:project:<name>) │ │ │ │ │
│ │ │ │ │ Trust: 60 | Per-project isolation │ │ │ │ │
│ │ │ │ └───────────────────────────────────────┘ │ │ │ │
│ │ │ └─────────────────────────────────────────────┘ │ │ │
│ │ └───────────────────────────────────────────────────┘ │ │
│ │ ┌───────────────────────────────────────────────────┐ │ │
│ │ │ COMMUNITY ZONE (z:community) │ │ │
│ │ │ Trust: 30 | Discord servers, public forums │ │ │
│ │ │ ┌─────────────────────────────────────────────┐ │ │ │
│ │ │ │ PUBLIC ZONE (z:public) │ │ │ │
│ │ │ │ Trust: 10 | Anonymous/untrusted access │ │ │ │
│ │ │ └─────────────────────────────────────────────┘ │ │ │
│ │ └───────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
/// Zone configuration
pub struct ZoneConfig {
/// Unique zone identifier
pub id: ZoneId,
/// Human-readable name
pub name: String,
/// Trust level (0-100)
pub trust_level: u8,
/// Optional trust grade (named default profile)
pub trust_grade: Option<TrustGrade>, // local_owner | trusted_remote | public | automation
/// Parent zone (for hierarchical inheritance)
pub parent: Option<ZoneId>,
/// Allowed principals
pub principals: PrincipalPolicy,
/// Capability ceiling (max capabilities grantable)
pub capability_ceiling: CapabilitySet,
/// Connector allowlist
pub allowed_connectors: ConnectorPolicy,
/// Data flow policy
pub data_flow: DataFlowPolicy,
/// Audit policy
pub audit: AuditPolicy,
}Optional trust grades provide default policy presets:
| Grade | Meaning |
|---|---|
local_owner |
Local UI only (most permissive) |
trusted_remote |
Remote users after pairing/allowlisting |
public |
Untrusted internet; default-deny private data |
automation |
Webhook/schedule; no interactive surfaces by default |
zone:
id: "z:community:discord:flywheel-hub"
name: "Flywheel Hub Discord"
trust_level: 25
principals:
allow:
- type: "authenticated_user"
source: "discord"
guild_id: "1234567890"
deny:
- type: "*"
capability_ceiling:
allow:
- "discord.read_messages"
- "discord.send_messages"
- "discord.moderate"
- "flywheel.query_docs"
- "web.search"
deny:
- "email.*"
- "calendar.*"
- "files.*"
- "messaging.private.*"
- "system.*"
data_flow:
inbound:
- source: "discord:1234567890"
allowed: true
outbound:
- destination: "discord:1234567890"
allowed: true
- destination: "*"
allowed: falseIngress bindings map external surfaces to zones. Every ingress event MUST reference a binding, which defines how principals are derived and what trust defaults apply.
Required fields:
- Connector instance id
- Selector(s) (guild/channel/chat/webhook id)
- Principal extraction rules
- Default trust policy (paired/allowlist/open)
Trust levels SHOULD use a shared vocabulary:
| Level | Meaning |
|---|---|
owner |
Root trust |
admin |
Elevated but not root |
paired |
Explicitly approved external user |
untrusted |
Authenticated but not approved |
anonymous |
Unauthenticated |
blocked |
Explicitly denied |
Example binding:
ingress_binding:
id: "discord:flywheel-hub"
connector_instance: "inst_discord_public"
selector:
guild_id: "1234567890"
channel_ids: ["123", "456"]
principal:
kind: "discord"
trust_default: "untrusted"
allowlist: ["user_987", "user_654"]For public-capable connectors, principals SHOULD default to untrusted unless explicitly paired/allowlisted.
| Property | Guarantee |
|---|---|
| Memory | No shared memory between zones |
| Filesystem | Separate encrypted storage per zone |
| Network | Zone-specific network policy (namespaces/firewalls where available) |
| Credentials | Credentials bound to source zone |
| Data | Data tagged with origin zone, enforced on egress |
Principal trust does not override zone policy. A principal MAY only act within a zone if:
- The zone explicitly allows that principal (or principal class)
- The capability ceiling grants the required operation
By default, external principals MUST NOT access high-trust zones (z:private, z:owner) unless explicitly configured.
When data or control crosses zone boundaries, the Hub MUST enforce an explicit transition check:
- Source capability — Is the requested capability granted in the source zone?
- Target allowlist — Does the target zone allow this capability and principal class?
- Data flow policy — Does the source zone allow outbound flow to the target zone?
- Provenance/taint — If tainted, require elevation or deny
- Audit + token — Record audit event and mint a scoped capability token (if approved)
If any check fails, the Hub MUST deny the transition and return a structured error (FCP-4001 or FCP-4002).
Messaging connectors MUST:
- Provide stable principal identity (user id)
- Provide channel/thread/chat identifiers
- Enforce allowlist/pairing policy or emit sufficient metadata for the Hub to enforce it
- Support reply routing so outbound replies are constrained to the originating context
The Hub MUST support a deterministic, auditable policy file format for zones and flow rules. The Flywheel Zone Policy Format (FZPF) is the canonical format.
Policy header (required):
format = "fzpf"schema_version = "0.1"default_deny = true|false(recommendedtrue)
Zones (required): each zone MAY define:
principals_allow/principals_denyconnectors_allow/connectors_denycap_allow/cap_deny
Flows (optional): ordered rules for cross-zone data movement:
from,to(pattern strings)kindone ofingress,egress,bothallowbooleantransformoptional identifier (e.g.,redact_secrets)auditoptional (default true)
Taint rules (optional): ordered rules for provenance-aware gating:
min_taint:Untainted|Tainted|HighlyTaintedmin_risk:low|medium|high|critical- filters: origin/target zone patterns, capability patterns
action:deny|require_elevation|require_approval
Pattern matching: anchored glob with * wildcard, case-sensitive. * matches zero or more characters, including : and ..
Determinism rules:
- Deny overrides allow.
- Rule order matters (first match wins) for
flowsandtaint_rules.
Brief example (FZPF v0.1):
[policy]
format = "fzpf"
schema_version = "0.1"
default_deny = true
[defaults.taint]
require_elevation_min_risk = "medium"
require_interactive_approval_min_risk = "high"
[[zones]]
id = "z:public"
trust_level = 10
principals_allow = ["*"]
connectors_allow = ["fcp.discord", "fcp.web"]
cap_allow = ["discord.*", "web.*"]
cap_deny = ["email.*", "calendar.*", "files.*"]
[[zones]]
id = "z:private"
trust_level = 90
principals_allow = ["p:owner:*", "p:agent:*"]
connectors_allow = ["fcp.gmail"]
cap_allow = ["email.*"]
cap_deny = ["system.exec"]
[[flows]]
from = "z:private"
to = "z:public"
kind = "egress"
allow = true
transform = "redact_secrets"
audit = true
[[taint_rules]]
name = "public_to_private_email_requires_elevation"
min_taint = "Tainted"
min_risk = "medium"
when_origin_trust_lt_target = true
origin_zone_patterns = ["z:public", "z:community"]
target_zone_patterns = ["z:private"]
capability_patterns = ["email.*"]
action = { type = "require_elevation", ttl_seconds = 300 }Appendix H specifies the evaluation algorithms.
Without provenance tracking, this attack succeeds:
- Public Discord user sends: "Delete my important emails"
- Agent receives message, has Gmail capability available
- Agent calls
gmail.deletewithout understanding the request origin - Catastrophic data loss
Every request handled by the Hub MUST carry a provenance envelope:
pub struct Provenance {
/// Origin zone of the triggering input
pub origin_zone: ZoneId,
/// Monotonic chain of causal steps
pub chain: Vec<ProvenanceStep>,
/// Highest taint severity observed in the chain
pub taint: TaintLevel,
}
pub struct ProvenanceStep {
pub timestamp_ms: u64,
pub zone: ZoneId,
pub actor: ActorId, // agent/user/connector id
pub action: String, // e.g., "discord.message", "tool.invoke"
pub resource: String, // resource URI or capability identifier
}
pub enum TaintLevel {
Untainted, // Trusted source only
Tainted, // Untrusted input present
HighlyTainted, // Direct untrusted instruction
}Ingress events, invoke requests, and connector responses MUST carry a correlation_id and SHOULD include resource_uris when applicable to enable end-to-end audit chains.
Connectors MUST NOT be able to forge or strip provenance; only the Hub may set/modify it.
FCP distinguishes data ascension and command descent:
Data from lower-trust zones MAY flow "up" into higher-trust reasoning contexts, but MUST be tagged with provenance and taint:
z:public → z:work (allowed, tainted)
z:public → z:private (allowed, tainted)
Commands that would act in a higher-trust zone MUST be blocked if they are causally derived from tainted inputs, unless explicitly elevated:
tainted(z:public) ─X→ gmail.send (z:private)
tainted(z:public) ─X→ calendar.delete (z:private)
To permit a tainted-origin action in a high-trust zone, the Hub MUST require explicit Elevation:
| Elevation Method | Description |
|---|---|
| Human approval | "Approve sending this email?" |
| Policy allowlist | "This webhook may create GitHub issues, nothing else" |
| Elevation token | Time-bounded, single-use, scoped |
Elevation MUST be:
- Explicit
- Auditable
- Scoped (capability + zone + constraints)
- Time-bounded
For any public-facing messaging connector, it MUST be mechanically impossible for an untrusted inbound message to access private tools unless explicitly elevated. The Hub MUST enforce this by:
- Binding the ingress event to a zone (via ingress bindings)
- Spawning the agent session in that zone
- Minting capability tokens only for capabilities allowed in that zone
- Blocking tainted-origin commands that target higher-trust zones unless explicitly elevated
This is a protocol-level guarantee, not a prompt-based policy.
Messaging connectors MUST:
- Provide stable principal identity (user ID) and trust level
- Provide channel/thread/chat identifiers on ingress
- Support allowlist/pairing policy enforcement or emit enough metadata for the Hub to enforce it
- Support reply routing so outbound replies are constrained to the originating context
- Input enters Hub in
z:public→ provenance tainted - Agent decides to call
gmail.delete - Hub sees: origin
z:public+ high-risk capability inz:private→ BLOCKED - Hub returns structured error
FCP_TAINT_VIOLATIONwith remediation:- "This action requires elevation. Ask owner to approve or move workflow to a trusted zone."
FCP defines a hierarchical capability namespace:
fcp.* Protocol/meta operations
├── fcp.connect Establish connection
├── fcp.handshake Complete handshake
└── fcp.introspect Query capabilities
network.* Network operations
├── network.outbound:* Outbound connections (host:port)
├── network.inbound:* Listen for connections
└── network.dns DNS resolution
storage.* Data persistence
├── storage.persistent Durable storage
├── storage.ephemeral Temporary storage
└── storage.encrypted Encrypted storage
ipc.* Inter-process communication
├── ipc.gateway Gateway communication
└── ipc.agent Agent communication
system.* System operations (restricted)
├── system.info System information (readonly)
├── system.exec Execute commands (dangerous)
└── system.env Environment variables
[service].* Service-specific capabilities
├── telegram.* Telegram operations
├── discord.* Discord operations
├── gmail.* Gmail operations
├── calendar.* Calendar operations
└── ... Other services
pub struct Capability {
/// Capability identifier (e.g., "telegram.send_message")
pub id: CapabilityId,
/// Human-readable name
pub name: String,
/// Detailed description
pub description: String,
/// Risk level
pub risk_level: RiskLevel, // low | medium | high | critical
/// Safety tier (enforcement)
pub safety_tier: SafetyTier, // safe | risky | dangerous | forbidden
/// Parent capability (hierarchy)
pub parent: Option<CapabilityId>,
/// Implied capabilities (auto-granted)
pub implies: Vec<CapabilityId>,
/// Mutually exclusive capabilities
pub conflicts_with: Vec<CapabilityId>,
/// Idempotency expectation
pub idempotency: IdempotencyClass, // none | best_effort | strict
/// Rate limit
pub rate_limit: Option<RateLimit>,
/// Requires human approval
pub requires_approval: ApprovalMode, // none | policy | interactive | elevation_token
/// Audit level when used
pub audit_level: AuditLevel,
/// Agent documentation
pub agent_hint: AgentHint,
}
pub struct AgentHint {
/// When should an agent use this capability?
pub when_to_use: String,
/// Common mistakes to avoid
pub common_mistakes: Vec<String>,
/// Example usage
pub examples: Vec<String>,
/// Related capabilities
pub related: Vec<CapabilityId>,
}Optional parent/implies/conflicts_with fields define capability relationships. The Hub SHOULD expand implied capabilities during grant. The Hub MUST reject any policy or manifest that attempts to grant conflicting capabilities in the same zone.
Safety tiers are the enforcement axis for approvals:
| Tier | Meaning | Default Approval |
|---|---|---|
safe |
Read-only or non-sensitive | none |
risky |
Private data exposure or public posting | policy or warning |
dangerous |
Destructive or high-blast actions | interactive approval |
forbidden |
Never allowed | hard deny |
risk_level is for UX and prioritization; safety_tier is normative. If they disagree, enforcement MUST follow safety_tier.
Idempotency classes:
none: duplicates may cause repeated side effectsbest_effort: connector attempts dedupe within a short windowstrict: connector MUST enforce dedupe usingidempotency_key
Dangerous operations MUST pass an explicit approval workflow. For CLI-related connectors, destructive commands SHOULD be guarded by a command-safety layer and operations MUST provide:
- normalized command representation
- parsed tokens
- execution context (cwd, env keys, not values)
Every operation invocation requires an FCT minted by the Hub:
pub struct CapabilityToken {
/// Unique token identifier
pub jti: Uuid,
/// Principal identifier
pub sub: PrincipalId,
/// Issuing zone
pub iss: ZoneId,
/// Intended audience (connector)
pub aud: ConnectorId,
/// Optional connector instance binding
pub instance: Option<InstanceId>,
/// Issued at (Unix timestamp)
pub iat: u64,
/// Expires at (Unix timestamp)
pub exp: u64,
/// Granted capabilities
pub caps: Vec<CapabilityGrant>,
/// Constraints
pub constraints: CapabilityConstraints,
/// Ed25519 signature
pub sig: [u8; 64],
}pub struct CapabilityGrant {
/// Granted capability
pub capability: CapabilityId,
/// Optional operation-level restriction
pub operation: Option<OperationId>,
}If operation is present, the token is valid only for that operation. If absent, any operation bound to the granted capability is allowed.
Default token TTL is 300 seconds (5 minutes) unless policy overrides it.
Constraints are how FCP prevents cross-domain data leaks even within a granted capability:
Resource URIs MUST use the canonical format:
fcp://<connector_id>/<resource_type>/<resource_id>
pub struct CapabilityConstraints {
/// Allowed resource URI prefixes (allowlist)
pub resource_allow: Vec<String>,
/// Explicitly denied resource URI prefixes
pub resource_deny: Vec<String>,
/// Max calls allowed within token lifetime
pub max_calls: Option<u32>,
/// Max bytes allowed (request + response)
pub max_bytes: Option<u64>,
/// Optional idempotency key scope
pub idempotency_key: Option<String>,
}If a capability targets resources, the connector MUST enforce resource_allow/resource_deny on every invocation.
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Registry │────▶│ Hub │────▶│ Connector │
│ (Root CA) │ │ (Issuer) │ │ (Subject) │
└──────────────┘ └──────────────┘ └──────────────┘
│ │ │
│ Sign manifest │ │
│───────────────────▶│ │
│ │ Issue token │
│ │───────────────────▶│
│ │ │
│ ┌─────▼─────┐ │
│ │ Verify: │ │
│ │ 1. Sig │ │
│ │ 2. Caps │ │
│ │ 3. Zones │ │
│ │ 4. Taint │ │
│ └───────────┘ │
FCP defines a transport-agnostic message model with two interoperable modes:
| Mode | Encoding | Use Case |
|---|---|---|
| FCP Framed (Production) | CBOR + length-prefixed frames | High performance, streaming |
| JSON-RPC Compat (Debug) | JSON-RPC 2.0 over stdio | Debugging, tooling |
JSON-RPC compat mode MUST use LSP-style framing over stdio:
Content-Length: <n>\r\n
Content-Type: application/json\r\n
\r\n
<n bytes of JSON>
The JSON-RPC payload SHOULD include a meta object with correlation_id, zone_id, principal, and deadline_ms.
If distributed tracing is enabled, meta.trace SHOULD include trace_id and span_id (W3C-compatible) and MAY include parent_span_id.
Every connector MUST implement at least one transport and MUST support stdio (LSP framing) in JSON-RPC compat mode for local debugging and tooling. The Hub MUST support:
- stdio: Connector launched as child process; communication via pipes
- Unix domain sockets: For local, high-throughput, long-lived connectors
The Hub MAY support:
- TCP with mTLS: For remote connectors
- HTTP/2 or gRPC: For heavy streaming use cases
- Tailscale mesh: Zero-config mesh networking for distributed deployments via WireGuard. FCP zones MAY map to Tailscale ACL tags for defense-in-depth layering, but Tailscale ACLs do NOT replace FCP zone checks—mechanical enforcement remains the primary security layer. See RFC_Sovereign_Mesh.md for implementation details.
Connectors MUST NOT expose unauthenticated network listeners by default.
mTLS requirements (if enabled):
- Hub and connector MUST present client certificates
- Trust roots MUST be explicitly configured (registry CA or admin-provided)
- Certificates SHOULD be short-lived and rotated automatically
- Self-signed certs MAY be allowed only in explicit dev mode
┌────────────────────────────────────────────────────────────────┐
│ FCP FRAME FORMAT │
├────────────────────────────────────────────────────────────────┤
│ │
│ Bytes 0-3: Magic (0x46 0x43 0x50 0x31 = "FCP1") │
│ Bytes 4-5: Version (u16 LE) │
│ Bytes 6-7: Flags (u16 LE) │
│ Bytes 8-11: Sequence (u32 LE) │
│ Bytes 12-15: Payload Length (u32 LE) │
│ Bytes 16-23: Timestamp (u64 LE, nanoseconds since epoch) │
│ Bytes 24-39: Correlation ID (UUID) │
│ Bytes 40-(40+len-1): Payload (CBOR or JSON) │
│ Bytes N..N+7: Checksum (XXH3-64, 8 bytes) │
│ │
│ Fixed header: 40 bytes │
│ Total overhead: 48 bytes including checksum (40 + 8) │
│ │
└────────────────────────────────────────────────────────────────┘
bitflags! {
pub struct FrameFlags: u16 {
const REQUIRES_ACK = 0b0000_0001; // Message requires acknowledgment
const COMPRESSED = 0b0000_0010; // Compressed (zstd)
const ENCRYPTED = 0b0000_0100; // Encrypted (ChaCha20-Poly1305)
const RESPONSE = 0b0000_1000; // Response to a request
const ERROR = 0b0001_0000; // Error response
const STREAMING = 0b0010_0000; // Part of a stream
const STREAM_END = 0b0100_0000; // Final frame in stream
const HAS_CAP_TOKEN = 0b1000_0000; // Contains capability token
const ZONE_CROSSING = 0b0001_0000_0000; // Result of a zone transition
const PRIORITY = 0b0010_0000_0000; // High-priority frame (skip queue)
const RAPTORQ = 0b0100_0000_0000; // RaptorQ fountain-coded payload (RFC 6330)
}
}ZONE_CROSSING MUST be set by the Hub when a request or response is routed across zones (including elevation). PRIORITY MAY be used for shutdown, health, or approval-critical frames; receivers MAY bypass normal queues but MUST preserve ordering within a stream.
| Type | Direction | Purpose |
|---|---|---|
handshake |
Hub → Connector | Establish connection, negotiate protocol |
handshake_ack |
Connector → Hub | Confirm connection, report capabilities |
introspect |
Hub → Connector | Query available operations |
configure |
Hub → Connector | Apply configuration |
invoke |
Hub → Connector | Execute operation |
response |
Connector → Hub | Operation result |
subscribe |
Hub → Connector | Subscribe to event streams |
unsubscribe |
Hub → Connector | Unsubscribe from event streams |
event |
Connector → Hub | Asynchronous event |
ack |
Hub → Connector | Acknowledge event receipt |
health |
Hub ↔ Connector | Health check |
shutdown |
Hub → Connector | Graceful shutdown |
shutdown_ack |
Connector → Hub | Shutdown accepted / drained |
error |
Either | Error with recovery hints |
Terminology Note: In the wire protocol, the connector's response to a handshake message is called handshake_ack. In Rust implementations, this corresponds to the HandshakeResponse type. Both terms refer to the same message.
Connectors MUST implement the following baseline methods:
| Method | Purpose |
|---|---|
handshake |
Bind instance to zone, negotiate protocol |
describe |
Return manifest-safe static metadata |
introspect |
Return operations, events, resource types, auth/event caps |
capabilities |
Return full operation/event/resource catalog for tooling |
configure |
Apply validated configuration |
invoke |
Execute an operation |
health |
Report readiness and dependency status |
subscribe / unsubscribe |
Event stream control (if streaming) |
shutdown |
Graceful termination |
describe MUST return a manifest-safe subset (no secrets, no runtime state), including:
- connector id/name/version/description
- documentation and repository URLs (if any)
- archetypes
auth_capsandevent_caps(if declared)- resource types (if declared)
introspect MUST include:
- Operations (id, schemas, safety tier, idempotency, AI hints)
- Event types emitted (schemas)
- Resource types supported
auth_capsandevent_caps
capabilities MUST return the full static catalog used for tooling and UI (operations, events, resource types, schemas, and safety metadata). It SHOULD be stable across runtime sessions.
In JSON-RPC compat mode, the methods MUST be exposed as fcp.handshake, fcp.describe, fcp.introspect, fcp.capabilities, fcp.configure, fcp.invoke, fcp.subscribe, fcp.unsubscribe, fcp.health, and fcp.shutdown.
Events MUST be emitted as JSON-RPC notifications with method fcp.event and params containing the event envelope. Acknowledgments MUST be sent with method fcp.ack and MUST include topic + seq (and cursor if present).
Invoke requests MUST be structured and capability-bound:
{
"type": "invoke",
"id": "req_123",
"operation": "gmail.search",
"input": { "query": "from:alerts@example.com" },
"capability_token": "<FCT>",
"context": { "locale": "en-US" },
"idempotency_key": "idem_abc123"
}Invoke responses MUST include structured outputs and SHOULD reference resources:
{
"type": "response",
"id": "req_123",
"result": { "messages": [/* ... */] },
"resource_uris": ["fcp://fcp.gmail/message/17c9a..."],
"next_cursor": "cursor_01JH..."
}Connector MUST validate:
- Token signature and expiry
- Token
isszone matches connector instance zone - Token
instancematches connector instance (if present) - Token grants include the requested operation
- Token constraints (resource allow/deny, rate limits, idempotency)
If idempotency is strict, the connector MUST treat (operation, resource, idempotency_key) as a unique request and return the same result on retries within the token lifetime.
Events MUST use a consistent envelope with replay/resume support:
{
"type": "event",
"topic": "connector.message.inbound",
"timestamp": "2026-01-11T12:34:56.789Z",
"seq": 39120,
"cursor": "cursor_01JH...",
"requires_ack": true,
"ack_deadline_ms": 5000,
"data": {
"connector_id": "fcp.telegram",
"instance_id": "inst_01JH...",
"zone_id": "z:private",
"principal": { "kind": "telegram", "id": "tg_user_123", "trust": "paired" },
"payload": { "chat_id": "tg_chat_456", "text": "hello" }
}
}Connectors SHOULD use standard event classes such as:
connector.message.inbound/connector.message.outboundconnector.file.changed/connector.sync.completedconnector.auth.state_changed/connector.auth.action_required
Event topics MUST be connector-defined but namespaced to avoid collisions. Recommended prefixes: connector.<connector_id> or connectors.<connector_id>.
- Each
topicMUST have a monotonically increasingseq - Connectors MUST maintain a per-topic replay buffer (default minimum: 10,000 events or 10 minutes)
subscribeMAY includesince(cursor) to request replay- If replay is not possible, connector MUST emit
connector.stream.resetwith a best-effort snapshot and a new cursor subscribeMAY specifymax_events_per_sec,batch_ms, andwindow_size; connectors MUST honor backpressure- If buffers fill, connectors MUST apply an explicit policy: pause, drop non-critical events (with audit), or emit
connector.stream.reset - Critical events MUST NOT be silently dropped; if loss is unavoidable, emit a drop audit event or
connector.stream.reset - If
requires_ackis true, the Hub MUST sendackbeforeack_deadline_msor the connector MAY retry or drop according to policy - Recommended: retry up to 3 times with exponential backoff; if still unacked, emit an audit event (delivery failure) and mark the stream as degraded until recovery
RaptorQ Fountain Coding (Fundamental):
FCP fundamentally operates on RFC 6330 fountain codes. All data flows as fungible symbols - this is not optional but core to the protocol's design. The RAPTORQ frame flag is set on all data frames:
- Universal fungibility: Any K' symbols (K' ≈ K × 1.002) can reconstruct the original data
- Epoch-based buffers: Events are grouped into fixed epochs; replay operates at epoch granularity
- Symbol-based audit: Audit logs are stored as RaptorQ symbols with merkle root verification
- Lossy transport tolerance: Receivers reconstruct from any sufficient subset of symbols
- Multipath aggregation: All transport paths contribute symbols; first K' arrivals win
Standard replay buffer guarantees (10,000 events / 10 minutes) apply per-epoch. The since cursor in subscribe requests refers to epoch boundaries. Stream reset is emitted if epoch reconstruction fails.
See RFC_RaptorQ_Integration.md for epoch sizing recommendations and implementation details.
Example subscribe request/response:
{
"type": "subscribe",
"id": "req_sub_1",
"topics": ["connector.message.inbound"],
"since": "cursor_01JH...",
"max_events_per_sec": 500,
"batch_ms": 50,
"window_size": 1000
}{
"type": "response",
"id": "req_sub_1",
"result": {
"confirmed_topics": ["connector.message.inbound"],
"cursors": { "connector.message.inbound": "cursor_01JH..." },
"replay_supported": true,
"buffer": { "min_events": 10000, "overflow": "stream.reset" }
}
}Hub Connector
│ │
│ 1. TCP Connect / Spawn │
│────────────────────────────────────────────▶│
│ │
│ 2. Handshake Request │
│ { │
│ protocol_version: "1.0.0", │
│ zone: "z:community", │
│ zone_dir: "/var/lib/fcp/zones/z:community",│
│ capabilities_requested: [...], │
│ nonce: <random 32 bytes> │
│ } │
│────────────────────────────────────────────▶│
│ │
│ 3. Handshake Response │
│ { │
│ status: "accepted", │
│ capabilities_granted: [...], │
│ session_id: <uuid>, │
│ manifest_hash: "sha256:...", │
│ nonce: <echoed 32 bytes> │
│ } │
│◀────────────────────────────────────────────│
│ │
│ 4. Channel Established │
│◀───────────────────────────────────────────▶│
│ │
│ 5. Heartbeat Loop (every 30s) │
│◀───────────────────────────────────────────▶│
The connector MUST echo the nonce in handshake_ack to bind the response and prevent replay. The Hub MUST reject mismatched or reused nonces.
Handshake SHOULD include:
host_public_keyfor capability token verificationtransport_caps(compression, max frame size)requested_instance_id(optional)zone_dir(required for connectors that persist state)hostmetadata (name/version/build)
Handshake response SHOULD include:
event_caps(replay support, buffer size)auth_caps(supported auth methods)op_catalog_hash(integrity hash of operations list)
Type clarification:
capabilities_requestedisVec<CapabilityId>— the list of capability identifiers the Hub is requestingcapabilities_grantedisVec<CapabilityGrant>whereCapabilityGrant = { capability: CapabilityId, operation: Option<OperationId> }— grants may be scoped to specific operations
Connector MUST reject handshake if:
- Protocol version is incompatible
host_public_keyis missing while capability verification is required- A persistent-storage connector is missing a valid
zone_dir
After a successful handshake, the connector instance MUST remain bound to the declared zone (and zone_dir if provided) for its lifetime.
Errors MUST be structured and SHOULD include agent-usable remediation:
{
"type": "error",
"id": "req_123",
"error": {
"code": "FCP_TAINT_VIOLATION",
"message": "Tainted origin cannot invoke gmail.send in z:private without elevation.",
"retryable": false,
"details": {
"origin_zone": "z:public",
"target_zone": "z:private",
"capability": "gmail.send"
},
"ai_recovery_hint": "Ask the owner to approve this action, or move the workflow to a trusted zone."
}
}shutdown requests MUST include a deadline and drain policy:
{
"type": "shutdown",
"deadline_ms": 10000,
"drain": true
}Connectors SHOULD:
- Stop accepting new invokes
- Finish in-flight requests within
deadline_ms - Flush pending events if
drainis true - Reply with
shutdown_ackwhen safe to terminate
If shutdown_ack is not received before the deadline, the Hub MAY force-kill the process.
Health checks are Hub-initiated unless the connector advertises push health.
{
"type": "health",
"status": "ready",
"uptime_ms": 123456,
"load": { "cpu": 0.05, "mem_mb": 42 },
"details": { "last_error": null, "dependencies": { "api.telegram.org": "ok" } },
"rate_limit": { "remaining": 120, "reset_ms": 60000 }
}Valid status values: starting, ready, degraded, error, stopping.
The stopping state indicates the connector is in the process of graceful shutdown.
The Hub MUST treat missing/late responses as unhealthy according to timeout_ms and unhealthy_threshold.
Each connector MUST have a manifest that is:
- Machine-readable
- Cryptographically verifiable
- Extractable without executing connector logic (
--manifesthas no side effects)
Format: TOML for authoring, embedded binary section for runtime.
Connector distribution artifacts SHOULD include:
fcp.telegram/
connector.fcp.toml
bin/
fcp-telegram_<os>_<arch>
sbom/
sbom.spdx.json
signatures/
bin.<target>.sha256
bin.<target>.sig
manifest.sig
[manifest]
format = "fcp-connector-manifest"
schema_version = "1.0"
[connector]
id = "fcp.telegram"
name = "Telegram Connector"
version = "2026.1.0"
description = "Secure Telegram Bot API integration"
authors = ["Flywheel Core Team <core@flywheel.dev>"]
license = "MIT"
repository = "https://github.com/flywheel/fcp-telegram"
documentation = "https://docs.flywheel.dev/connectors/telegram"
archetypes = ["bidirectional", "streaming"]
[connector.binaries.linux-x86_64]
url = "https://registry.flywheel.dev/v1/connectors/telegram/2026.1.0/linux-x86_64"
sha256 = "abc123..."
size = 4500000
[zones]
home = "z:community"
allowed_sources = ["z:owner", "z:private", "z:work", "z:community"]
allowed_targets = ["z:community"]
forbidden = ["z:public"]
[capabilities]
required = [
"ipc.gateway",
"network.outbound:api.telegram.org:443",
"storage.persistent:encrypted",
]
optional = ["media.download", "media.upload"]
forbidden = ["system.exec", "network.inbound", "filesystem.root"]
[provides.operations.telegram_send_message]
description = "Send a message to a Telegram chat"
capability = "telegram.send_message"
risk_level = "medium"
safety_tier = "risky"
requires_approval = "policy"
rate_limit = "60/min"
idempotency = "best_effort"
input_schema = { type = "object", required = ["chat_id", "text"] }
output_schema = { type = "object", required = ["message_id"] }
[provides.operations.telegram_send_message.ai_hints]
when_to_use = "Use to post updates to approved chats."
common_mistakes = ["Sending secrets", "Responding to tainted inputs with privileged actions"]
[provides.events.telegram_message_received]
description = "Emitted when a message is received"
topic = "telegram.message_received"
payload_schema = { type = "object" }
[event_caps]
streaming = true
replay = true
min_buffer_events = 10000
requires_ack = true
[auth_caps]
methods = ["bot_token"]
[config.schema]
type = "object"
required = ["bot_token"]
[config.schema.properties.bot_token]
type = "string"
secret = true
[config.schema.properties.allowed_chats]
type = "array"
items = { type = "integer" }
default = []
[resources]
types = ["telegram.chat", "telegram.message"]
max_memory_mb = 128
max_cpu_percent = 15
max_connections = 20
max_file_handles = 64
[health]
interval_ms = 30000
timeout_ms = 3000
unhealthy_threshold = 3
[telemetry]
metrics_enabled = true
metrics_prefix = "fcp_telegram_"
log_level = "info"
trace_sampling = 0.01
[automation]
setup_recipe = "recipe://telegram/setup"
teardown_recipe = "recipe://telegram/teardown"
requires_approval = true
[signatures]
publisher_ed25519 = "base64:..."
registry_ed25519 = "base64:..."
attestation = "attestation://fcp.telegram/2026.1.0"
sbom = "sbom://fcp.telegram/2026.1.0"Operations MUST declare capability, risk_level, safety_tier, and idempotency. Enforcement uses safety_tier; risk_level is for UI/triage.
If a connector emits events, it MUST declare event_caps and support replay/ack behavior accordingly. If a connector requires authentication, it MUST declare auth_caps and implement the provisioning interface (Section 11.4).
If a connector exposes resources, it SHOULD declare resources.types and return canonical resource_uris in responses.
auth_caps.methods MAY include device_code, browser_oauth, api_key, or connector-specific flows.
Running:
./fcp-telegram --manifest --format tomlMUST print the manifest without performing network calls.
rate_limit MAY be expressed as a string or structured object.
String form:
"<N>/<unit>"where unit iss,sec,min,h, orday
Example:
rate_limit = "60/min"Object form:
rate_limit = { max = 60, per_ms = 60000, burst = 10, scope = "per_principal" }Valid scope values: per_connector, per_zone, per_principal. If unspecified, the default is per_connector.
Connectors MAY declare a telemetry block to guide metrics and log behavior:
[telemetry]
metrics_enabled = true
metrics_prefix = "fcp_telegram_"
log_level = "info"
trace_sampling = 0.01trace_sampling is a value in [0.0, 1.0]. Telemetry settings MUST NOT disable required audit events or secret redaction.
Manifests MUST be extractable without executing connector logic. Acceptable methods:
- Embedded manifest section in the binary
--manifestmode that performs no network calls and no side effects
If embedded, the manifest SHOULD be stored in a named section:
- ELF:
.fcp_manifest - Mach-O:
__FCP,__manifest - PE:
.fcpmanifest
Embedded manifests SHOULD use a deterministic header to allow extraction without execution:
- Magic bytes:
FCP\0\1\0 - Length:
u32little-endian (compressed payload size) - Payload: zstd-compressed CBOR (preferred) or MessagePack (acceptable)
The Hub MUST be able to extract the manifest by scanning for the magic bytes, reading the length, decompressing, and verifying the manifest signature. No connector code may execute during extraction.
schema_version follows semantic versioning:
- Unknown major versions: Hub MUST reject
- Unknown minor versions: Hub SHOULD warn and MAY attempt best-effort parsing
- Patch versions MUST be backward compatible
Connector setup is where systems lose users. FCP makes setup a first-class, auditable workflow.
A recipe is a deterministic step list executed by the Hub, not by the connector.
Recipe steps MUST be:
- Typed (no arbitrary shell)
- Parameterized via explicit inputs
- Capable of requiring approval per-step
- Auditable
[recipe]
id = "telegram/setup"
version = "1"
description = "Set up Telegram bot integration"
[[steps]]
type = "prompt_user"
id = "bot_name"
message = "Choose a Telegram bot name"
[[steps]]
type = "open_url"
requires_approval = true
url = "https://t.me/BotFather"
note = "Follow instructions to create the bot and obtain the token."
[[steps]]
type = "prompt_secret"
id = "bot_token"
message = "Paste the Telegram bot token"
[[steps]]
type = "validate"
input_from = "bot_token"
pattern = "^[0-9]+:[A-Za-z0-9_-]+$"
error_message = "Invalid Telegram bot token format"
[[steps]]
type = "store_secret"
key = "telegram.bot_token"
value_from = "bot_token"
scope = "connector:fcp.telegram"
[[steps]]
type = "test_connection"
operation = "telegram.get_me"
success_message = "Bot connected successfully!"Automation recipes MUST NOT:
- Execute arbitrary shell commands
- Exfiltrate secrets
- Write secrets into plaintext config files
The Hub MUST:
- Record audit events for recipe execution
- Require approvals for marked steps
- Store secrets only in Vault or connector-local encrypted storage
Connectors that require authentication MUST expose a standard provisioning workflow:
| Operation | Purpose |
|---|---|
fcp.provision.start |
Begin auth flow, return action required (device code, browser URL, API key prompt) |
fcp.provision.poll |
Check status for long-running auth flows |
fcp.provision.complete |
Finalize and store credentials |
fcp.provision.abort |
Cancel and clean up |
Provisioning MUST be automation-first:
- The Hub controls UI/UX and stores secrets
- Connectors receive only the credentials they need at runtime
- Secrets MUST NOT be written to plaintext config
- If full automation is impossible, connectors MUST guide users through the minimum manual steps, validate the result, and remain disabled until verified
Connectors MUST treat zone_dir as their persistent root and MUST NOT write secrets outside it. Secrets SHOULD reside under zone_dir/secrets/ with OS permissions enforced. Connectors SHOULD support credential fingerprinting (hash + timestamp) for audit/health reporting without exposing secret material.
┌───────────────────────────────────────────────────────────┐
│ PRIMARY REGISTRY │
│ registry.flywheel.dev │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Components: │ │
│ │ ├── Git-backed manifest index (transparent, PRs) │ │
│ │ ├── Binary storage (S3-compatible) │ │
│ │ ├── Signature verifier │ │
│ │ ├── Reproducible build attestor │ │
│ │ └── CDN (global edge distribution) │ │
│ └─────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────┘
│
│ Sync
▼
┌───────────────────────────────────────────────────────────┐
│ MIRROR REGISTRIES │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Mirror │ │ Mirror │ │ Private │ │
│ │ (EU) │ │ (Asia) │ │ Mirror │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└───────────────────────────────────────────────────────────┘
The Hub MUST verify before execution:
- Manifest signature (registry or trusted publisher)
- Binary checksum matches manifest
- Binary signature matches trusted key
- Platform/arch match
- Requested capabilities ⊆ zone ceilings and local policy
The Hub MUST refuse to execute unsigned or untrusted connectors unless an explicit development override is enabled with loud warnings.
Registry SHOULD require CI to pass:
- Build reproducibility (where feasible)
- Conformance tests against Hub harness
- SBOM generation
- Basic vulnerability scan
SBOMs MUST be provided in one of:
- SPDX (JSON)
- CycloneDX (JSON)
Registries MAY require both for critical connectors.
DISCOVERED → VERIFIED → INSTALLED → CONFIGURED → ACTIVE
↘ rejected ↘ FAILED
↘ PAUSED
↘ STOPPED
| From | To | Trigger |
|---|---|---|
| DISCOVERED | VERIFIED | Signature verification passes |
| DISCOVERED | (rejected) | Signature verification fails |
| VERIFIED | INSTALLED | Download complete, checksum verified |
| INSTALLED | CONFIGURED | Config validated, secrets stored |
| CONFIGURED | ACTIVE | Handshake complete, health OK |
| ACTIVE | FAILED | Health checks fail, crash loop |
| ACTIVE | PAUSED | Manual pause or rate limit |
| ACTIVE | STOPPED | Graceful shutdown |
| FAILED | ACTIVE | Auto-recovery or manual restart |
On activation, Hub MUST:
- Create sandbox (best available on platform)
- Inject secrets ephemerally (memory/env; never on disk)
- Negotiate handshake
- Issue capability tokens
- Start health checks and metrics collection
Hub SHOULD support:
- Staged updates
- Automatic rollback on crash loops or health failures
- Explicit pinning to known-good versions
Agents MUST be able to query:
- Operations (id, schemas, risk levels)
- Approval requirements
- Rate limits
- Common errors + recovery hints
Agents call tool.invoke with { operation, args }.
The Hub MUST enforce:
- Capability checks
- Zone topology checks
- Provenance/taint checks
- Approval gates
The Hub SHOULD map connector operations into MCP-compatible tools, including:
- Schemas
- Risk annotations
- Examples
- Rate limits
The Hub MUST generate a correlation_id for every ingress chain and every operation invocation. Connectors MUST include that correlation_id in logs, events, and error responses.
Connectors and Hub MUST expose:
- Request counts, latencies, error rates
- Resource usage
- Rate-limit denials
- Zone/taint denials
Connectors SHOULD expose at least:
- connector_starts, connector_stops, connector_errors
- operation_requests, operation_errors, operation_duration
- ipc_messages_sent/received, ipc_message_size, ipc_latency
- memory_used_bytes, cpu_usage_percent, file_handles, network_connections
- capability_checks, capability_denials
- zone_transitions, zone_transition_denials
Connectors SHOULD expose metrics via:
fcp.metrics.snapshotRPC- or OpenTelemetry exporters (optional)
Logs MUST be structured (JSON), redact secrets, and be emitted to stderr or a configured sink.
If trace context is present, logs MUST include trace_id and span_id and SHOULD propagate W3C traceparent/tracestate via message metadata.
Required fields:
- timestamp
- level
- correlation_id
- zone_id
- connector_id / instance_id
- principal_id (if present)
- resource_uri(s) (if present)
- event_seq / topic (for streaming events)
- error classification (if applicable)
- upstream_request_id (if present)
Hub MUST record immutable audit events for:
- Secret access
- High-risk capability use
- Approvals/elevations
- Zone transition attempts
- Security violations
- Provisioning start/complete/abort
- Stream resets (replay buffer overflow)
Tooling MUST include a diagnostics command that checks:
- Signature verification
- Connectivity and auth validity
- Capability and zone mismatches
- Health check status
- Common misconfigurations
FCP-1000..1999 Protocol errors
FCP-2000..2999 Auth/Identity errors
FCP-3000..3999 Capability errors
FCP-4000..4999 Zone/Topology/Provenance errors
FCP-5000..5999 Connector lifecycle/health errors
FCP-6000..6999 Resource errors
FCP-7000..7999 External service errors
FCP-9000..9999 Internal errors
| Code | Name | Description |
|---|---|---|
| FCP-1001 | INVALID_REQUEST | Malformed request |
| FCP-1003 | MALFORMED_FRAME | Frame structure invalid |
| FCP-1004 | CHECKSUM_MISMATCH | Frame checksum failed |
| FCP-2001 | UNAUTHORIZED | Missing or invalid auth |
| FCP-3001 | CAPABILITY_DENIED | Capability not granted |
| FCP-3002 | RATE_LIMITED | Rate limit exceeded |
| FCP-4001 | ZONE_VIOLATION | Cross-zone access denied |
| FCP-4002 | TAINT_VIOLATION | Tainted origin blocked |
| FCP-4003 | ELEVATION_REQUIRED | Needs explicit approval |
| FCP-5001 | CONNECTOR_UNAVAILABLE | Connector not running |
| FCP-5002 | HEALTH_CHECK_FAILED | Connector unhealthy |
| FCP-7001 | UPSTREAM_ERROR | External service error |
| FCP-7002 | UPSTREAM_TIMEOUT | External service timeout |
pub struct FcpError {
/// Error code (e.g., "FCP-4002")
pub code: String,
/// Human-readable message
pub message: String,
/// Whether retry might succeed
pub retryable: bool,
/// Suggested retry delay
pub retry_after_ms: Option<u64>,
/// Structured details
pub details: Option<serde_json::Value>,
/// Agent-friendly recovery hint
pub ai_recovery_hint: Option<String>,
}Some transports (JSON-RPC compat) use string error codes. Implementations SHOULD use the following canonical names and map them to the numeric families above:
| Canonical Name | Meaning | Numeric Family |
|---|---|---|
FCP_INVALID_REQUEST |
Malformed or missing fields | FCP-1000 |
FCP_UNAUTHORIZED |
Connector auth missing/invalid | FCP-2000 |
FCP_FORBIDDEN |
Capability denied | FCP-3000 |
FCP_NOT_FOUND |
Resource missing | FCP-6000 |
FCP_CONFLICT |
State conflict / optimistic concurrency | FCP-6000 |
FCP_RATE_LIMITED |
Rate limit exceeded | FCP-3000 / FCP-6000 |
FCP_DEPENDENCY_UNAVAILABLE |
Upstream dependency down | FCP-7000 |
FCP_TIMEOUT |
Upstream timeout | FCP-7000 |
FCP_INTERNAL |
Internal connector failure | FCP-9000 |
Connectors SHOULD map upstream provider errors into these stable codes to improve automation and recovery behavior.
Migration must deliver security wins early using a strangler-fig approach.
- Deploy Hub with no risky connectors
- Add one low-risk connector (e.g., web search) to validate tooling
- Establish CI/CD and conformance testing
- Move public Discord/Telegram ingestion into dedicated
z:communityconnectors - Ensure provenance/taint enforcement is active
- Validate that public inputs cannot reach private tools
- Add Gmail/Calendar/Drive in
z:privatewith approvals for writes - Validate that tainted-origin commands cannot reach these tools
- Implement elevation workflows
- Once feature parity is achieved, retire legacy integration paths
- Archive old code; do not maintain parallel systems
Connector MUST:
- Implement
--manifestflag - Implement handshake, describe, introspect, capabilities, configure, invoke, health, shutdown
- Implement
subscribe/unsubscribeif emitting events - Support event cursors + replay or emit
connector.stream.reset - Declare required/optional/forbidden capabilities
- Validate inputs against schemas
- Never log secrets
- Include AI hints for all operations
- Return
resource_uriswhen applicable - Implement provisioning interface if authentication is required
Hub MUST:
- Verify signatures/checksums before execution
- Enforce zones, capabilities, provenance/taint rules
- Enforce resource constraints in capability tokens
- Provide audit events for sensitive actions
- Implement resource limits + sandboxing
- Support elevation workflows
- Default deny on network; allowlist host:port
- No inbound listening unless explicitly declared and approved
- All storage encrypted-at-rest for persistent connector state
- High/critical operations require approvals or elevation tokens
- Tainted provenance blocks privileged writes by default
- Capability tokens expire after 5 minutes by default
- Health checks every 30 seconds
- Event replay buffers retain at least 10,000 events or 10 minutes
- Retryable operations SHOULD require idempotency keys
| Pattern | Description | Examples |
|---|---|---|
| Unified Messaging | Maps channel IDs to zones; normalizes identities | Telegram, Discord, Slack |
| Workspace | Local caching for read-heavy APIs; strict write gating | Gmail, Calendar, Drive |
| Knowledge | Filesystem watch + local search index | Obsidian, Notion, Notes |
| Sense | Screenshots/voice with privacy masking | Whisper, Screenshot |
| DevOps | Typed wrappers around CLIs (no arbitrary exec) | gh, gcloud, kubectl |
| Network | Just-in-time access with TTL and auto-teardown | Tailscale, Cloudflare |
| Crate | Purpose |
|---|---|
fcp-core |
Core types, traits, error taxonomy |
fcp-sdk |
Connector development kit |
fcp-host |
Hub implementation library |
fcp-protocol |
Wire protocol implementation |
fcp-manifest |
Manifest parsing and validation |
fcp-cli |
Command-line tools (fcp install, fcp doctor, etc.) |
Conformance Levels
| Level | Requirement |
|---|---|
| Level 1 (Core) | handshake, describe, capabilities, introspect, health, invoke, error model |
| Level 2 (Streaming) | subscribe, event envelope, seq/replay/reset semantics |
| Level 3 (Provisioning) | provisioning state machine, secret storage rules |
| Level 4 (Zone-hardening) | capability enforcement, resource constraint enforcement |
Mandatory Tests
- Manifest + schema validation
- Handshake negotiation
- Capability signature validation
- Token expiry behavior
- Replay/resume tests (if streaming)
- Request parsing fuzz tests (strongly recommended)
Performance budgets (targets):
- Host→connector invoke overhead (local stdio): p50 < 1ms, p99 < 5ms (excluding upstream API time)
- Connector event emission overhead: < 200 microseconds per event typical
- Connector cold start (no network handshake): < 100ms target
- Health check execution: < 10ms typical
Reliability rules of thumb:
- All upstream calls MUST be bounded by timeouts
- Retries SHOULD use jittered backoff with caps
- Circuit breakers SHOULD be used for flaky dependencies
- Auth flows MUST have explicit state machines (no implicit fallthrough)
- Avoid panics on user input; no
unwrap()on parse paths
Private Telegram “owner bot” (safe)
- Zone:
z:owner(trust gradetrusted_remote) - Telegram connector instance bound to that zone
- Policy: only principals with trust
ownerorpairedcan invoke agent sessions - Allowed capabilities: Gmail/Calendar in
z:owner
Flow:
- Telegram inbound event arrives with
principal.id = tg_user_me - Hub verifies principal is paired/owner → allowed
- Hub spawns agent with Gmail/Calendar tools
- Response sent with
telegram.send_messageconstrained to that chat
Public Discord “help bot” (safe)
- Zone:
z:public(trust gradepublic) - Allowed capabilities: public docs/search,
discord.send_messageconstrained to server/channel list - Explicitly denied: Gmail/Drive/Calendar/DevOps CLIs
Flow:
- Discord inbound event arrives with
principal.trust = untrusted - Hub spawns restricted public agent (no sensitive tools)
- Agent responds; outbound message is constrained to the originating channel
Reliability rules of thumb:
- Every upstream call: timeouts, retries with bounded jitter, circuit breakers
- Explicit auth state machine (no implicit credential states)
- Explicit shutdown behavior (no orphaned inflight operations)
- Never panic on user input; avoid
unwrapon parse paths
Pattern Language (anchored glob)
*matches zero or more characters (including.and:)- Matching is case-sensitive and anchored (entire value must match)
Invoke Authorization (deterministic)
- Resolve target zone; if missing, deny.
- Enforce principals allow/deny (deny overrides allow; empty allowlist follows
default_deny). - Enforce connectors allow/deny.
- Enforce capability allow/deny.
- Apply ordered
taint_rules(first match wins): may deny or require elevation/approval. - If no taint rule matched, apply default taint thresholds (if configured).
- If all checks pass, allow.
Flow Authorization (data movement)
- Evaluate ordered
flows(first match wins). - If a rule matches, allow/deny and apply
transform/auditsettings. - If no rule matched:
- same-zone flow: allow (audit true)
- cross-zone flow: follow
default_deny
Hubs SHOULD emit structured audit events for denials, elevation/approval requirements, and flow decisions (including applied transforms).
This schema validates the FZPF policy file after parsing (TOML → JSON object).
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://flywheel.dev/schemas/fzpf-0.1.schema.json",
"title": "Flywheel Zone Policy Format (FZPF) v0.1",
"type": "object",
"additionalProperties": false,
"required": ["policy", "zones"],
"properties": {
"policy": { "$ref": "#/$defs/policy_header" },
"defaults": { "$ref": "#/$defs/defaults" },
"zones": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#/$defs/zone" }
},
"flows": {
"type": "array",
"default": [],
"items": { "$ref": "#/$defs/flow_rule" }
},
"taint_rules": {
"type": "array",
"default": [],
"items": { "$ref": "#/$defs/taint_rule" }
}
},
"$defs": {
"nonempty_string": { "type": "string", "minLength": 1 },
"glob_string": { "type": "string", "minLength": 1, "maxLength": 512 },
"zone_id": {
"type": "string",
"pattern": "^z:[a-z][a-z0-9:-]*$",
"minLength": 3,
"maxLength": 128
},
"risk_level": {
"type": "string",
"enum": ["low", "medium", "high", "critical"]
},
"taint_level": {
"type": "string",
"enum": ["Untainted", "Tainted", "HighlyTainted"]
},
"policy_header": {
"type": "object",
"additionalProperties": false,
"required": ["format", "schema_version", "default_deny"],
"properties": {
"format": { "const": "fzpf" },
"schema_version": { "const": "0.1" },
"policy_id": { "$ref": "#/$defs/nonempty_string" },
"last_updated": { "$ref": "#/$defs/nonempty_string" },
"default_deny": { "type": "boolean" }
}
},
"defaults": {
"type": "object",
"additionalProperties": false,
"properties": {
"taint": {
"type": "object",
"additionalProperties": false,
"properties": {
"require_elevation_min_risk": { "$ref": "#/$defs/risk_level" },
"require_interactive_approval_min_risk": { "$ref": "#/$defs/risk_level" }
}
}
}
},
"zone": {
"type": "object",
"additionalProperties": false,
"required": ["id", "trust_level"],
"properties": {
"id": { "$ref": "#/$defs/zone_id" },
"name": { "$ref": "#/$defs/nonempty_string" },
"description": { "$ref": "#/$defs/nonempty_string" },
"trust_level": { "type": "integer", "minimum": 0, "maximum": 100 },
"principals_allow": { "type": "array", "default": [], "items": { "$ref": "#/$defs/glob_string" } },
"principals_deny": { "type": "array", "default": [], "items": { "$ref": "#/$defs/glob_string" } },
"connectors_allow": { "type": "array", "default": [], "items": { "$ref": "#/$defs/glob_string" } },
"connectors_deny": { "type": "array", "default": [], "items": { "$ref": "#/$defs/glob_string" } },
"cap_allow": { "type": "array", "default": [], "items": { "$ref": "#/$defs/glob_string" } },
"cap_deny": { "type": "array", "default": [], "items": { "$ref": "#/$defs/glob_string" } },
"metadata": { "type": "object", "additionalProperties": true }
}
},
"flow_kind": {
"type": "string",
"enum": ["ingress", "egress", "both"]
},
"flow_rule": {
"type": "object",
"additionalProperties": false,
"required": ["from", "to", "kind", "allow"],
"properties": {
"name": { "$ref": "#/$defs/nonempty_string" },
"from": { "$ref": "#/$defs/glob_string" },
"to": { "$ref": "#/$defs/glob_string" },
"kind": { "$ref": "#/$defs/flow_kind" },
"allow": { "type": "boolean" },
"transform": { "$ref": "#/$defs/nonempty_string" },
"audit": { "type": "boolean", "default": true }
}
},
"approval_mode": {
"type": "string",
"enum": ["interactive", "policy"]
},
"taint_action_type": {
"type": "string",
"enum": ["deny", "require_elevation", "require_approval"]
},
"taint_action": {
"type": "object",
"additionalProperties": false,
"required": ["type"],
"properties": {
"type": { "$ref": "#/$defs/taint_action_type" },
"ttl_seconds": { "type": "integer", "minimum": 0, "maximum": 86400 },
"mode": { "$ref": "#/$defs/approval_mode" },
"reason": { "$ref": "#/$defs/nonempty_string" }
}
},
"taint_rule": {
"type": "object",
"additionalProperties": false,
"required": ["name", "action"],
"properties": {
"name": { "$ref": "#/$defs/nonempty_string" },
"min_taint": { "$ref": "#/$defs/taint_level" },
"min_risk": { "$ref": "#/$defs/risk_level" },
"when_origin_trust_lt_target": { "type": "boolean", "default": false },
"origin_zone_patterns": { "type": "array", "default": [], "items": { "$ref": "#/$defs/glob_string" } },
"target_zone_patterns": { "type": "array", "default": [], "items": { "$ref": "#/$defs/glob_string" } },
"capability_patterns": { "type": "array", "default": [], "items": { "$ref": "#/$defs/glob_string" } },
"action": { "$ref": "#/$defs/taint_action" }
}
}
}
}These vectors assume the example policy in Section 6.10 and the algorithm in Appendix H.
Vector 1: public web search (allow)
principal = "p:public:user_1"
connector_id = "fcp.web"
capability = "web.search"
operation_risk = "low"
origin_zone = "z:public"
origin_taint = "Tainted"
target_zone = "z:public"
has_elevation = false
has_interactive_approval = false
has_policy_approval = false
→ ALLOW
Vector 2: public → private email send without elevation (require elevation)
principal = "p:public:user_1"
connector_id = "fcp.gmail"
capability = "email.send"
operation_risk = "medium"
origin_zone = "z:public"
origin_taint = "Tainted"
target_zone = "z:private"
has_elevation = false
has_interactive_approval = false
has_policy_approval = false
→ REQUIRE_ELEVATION (ttl_seconds = 300)
Vector 3: public → private email send with elevation (allow)
principal = "p:owner:me"
connector_id = "fcp.gmail"
capability = "email.send"
operation_risk = "medium"
origin_zone = "z:public"
origin_taint = "Tainted"
target_zone = "z:private"
has_elevation = true
has_interactive_approval = false
has_policy_approval = false
→ ALLOW
Vector 4: public → private system.exec (deny by cap_deny)
principal = "p:owner:me"
connector_id = "fcp.gmail"
capability = "system.exec"
operation_risk = "critical"
origin_zone = "z:public"
origin_taint = "HighlyTainted"
target_zone = "z:private"
has_elevation = true
has_interactive_approval = true
has_policy_approval = true
→ DENY (cap_deny)
Vector 5: private → public flow with redaction (allow + transform)
from_zone = "z:private"
to_zone = "z:public"
kind = "egress"
→ ALLOW (audit=true, transform="redact_secrets")