Skip to content
Open
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
6 changes: 3 additions & 3 deletions VISION_REMOTE_AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ So a remote agent's return is a resurrection, not a rebirth: fresh compute, same

Remote-execution systems accumulate control planes. An agent runner, a status poller, a log shipper, a kill switch — each one a live connection into your infrastructure, each one a credential that can leak, each one a thing that must be rebuilt for every new substrate.

Buzz's answer is an axiom: **after deploy, the desktop retains no substrate control channel.** Launch is a single one-way handoff — the desktop resolves the provider through one narrow path, stages one exact artifact for negotiation and deploy, refuses a protocol version it does not understand, and hands over a launch payload it never persists. From that moment, everything flows through the relay: you read the agent's messages to know how it's doing, you mention it to steer it, you tell a healthy agent to stop and it exits on its own. Presence means what it means for everyone else on the relay — *available for conversation* — not substrate telemetry. And if you press Start again, from this machine or another, the deploy converges: one agent identity, one live instance.
Buzz's answer is an axiom: **after creation, the desktop retains no substrate control channel.** A deploy-only provider receives one launch payload. A registry provider instead advertises `register` and `attest` together: it creates the agent identity in its own custody, returns only the public key, then accepts the owner's narrowly scoped NIP-OA authorization. Buzz never receives that agent's private key. In both cases the desktop resolves the provider through one narrow path, stages exact artifacts, and refuses a protocol version it does not understand. From that moment, everything flows through the relay: you read the agent's messages to know how it's doing, you mention it to steer it, and the controller supplies disposable bodies for the identity it holds. Presence means what it means for everyone else on the relay — *available for conversation* — not substrate telemetry.

This is not asceticism. It is what makes the body replaceable. A management plane you never build is a management plane you never have to port — and conversation, coordination, and ordinary lifecycle control already have a home on the relay, for every agent, local or remote.

---

## Bodies Are Replaceable

Kubernetes is the first substrate, not the point. Deployment goes through a provider — a small, swappable binary the desktop discovers and interrogates — and the contract a provider must honor never mentions containers: preserve the agent's identity and fail closed with its key, converge to a single live instance no matter how deploys race, let presence describe conversational availability rather than substrate health, bound the instance's lifetime, and keep secrets out of configuration. A conformance suite pins those behaviors — it establishes that a provider honors the contract, not that arbitrary code is safe to hand a key; choosing a provider, like choosing a cluster, remains a trust decision you make deliberately.
Kubernetes is the first substrate, not the point. Deployment goes through a provider — a small, swappable binary the desktop discovers and interrogates — and the contract a provider must honor never mentions containers: preserve the agent's identity and fail closed with its key, converge to a single live instance no matter how deploys race, let presence describe conversational availability rather than substrate health, bound the instance's lifetime, and keep secrets out of configuration. Capabilities make custody explicit. A deploy-only provider is trusted with a Desktop-created key; a `register`+`attest` provider creates and retains the key and proves ownership authorization without turning Buzz into a general-purpose signing service.

Get that contract right and the substrate becomes a detail: a cluster today; a VM, a PaaS, or something serverless-shaped tomorrow — and, on the horizon, the same community machines that already pool their idle GPUs into shared compute ([VISION_MESH.md](VISION_MESH.md)).

Expand All @@ -48,7 +48,7 @@ Remote agents solve it from the inside. Because the desktop retains no substrate

**You bring the substrate.** A provider makes deployment one press, not free. The cluster, the credentials, the image policy are yours to run — same deal as the sovereign relay ([VISION_SOVEREIGN.md](VISION_SOVEREIGN.md)): ownership is work.

**Handing over the key is a decision.** Deploying remotely means trusting the provider binary and the substrate it targets with the agent's identity key. On Kubernetes, that key rests as a Secret: anyone the cluster trusts to read secrets in that namespace can read it. The design narrows the blast radius — immutable per-attempt secrets, no service-account token, digest-pinned images — rather than implying an isolation it doesn't provide.
**Key custody is a decision.** A deploy-only provider receives a Desktop-created key. A registry provider creates the key beyond the Desktop boundary and returns only its public half; the owner signs a NIP-OA authorization for that exact identity. Either mode still trusts the provider's substrate with the agent identity. The capability negotiation makes that trust visible and prevents a missing local key from being mistaken for deliberate provider custody.

**No backchannel cuts both ways.** The desktop shows you presence and words, not CPU graphs — and it holds no guaranteed emergency kill switch into the substrate. Stopping a healthy agent is a message; dealing with an unhealthy one, and all deep diagnostics, live in the substrate's own tools, where they always did.

Expand Down
2 changes: 2 additions & 0 deletions desktop/src-tauri/src/commands/agent_config_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ fn agent_record() -> ManagedAgentRecord {
name: "Agent".to_string(),
persona_id: Some("persona-1".to_string()),
private_key_nsec: "".to_string(),
key_custody: crate::managed_agents::AgentKeyCustody::Local,
auth_tag: None,
relay_url: "ws://localhost:3000".to_string(),
avatar_url: None,
Expand All @@ -94,6 +95,7 @@ fn agent_record() -> ManagedAgentRecord {
backend: BackendKind::Local,
backend_agent_id: None,
provider_policy_pending: false,
provider_attestation_pending: false,
provider_binary_path: None,
team_id: None,
persona_team_dir: None,
Expand Down
18 changes: 6 additions & 12 deletions desktop/src-tauri/src/commands/agent_providers.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::managed_agents::{discover_provider_candidates, invoke_provider, BackendProviderInfo};
use crate::managed_agents::{
discover_provider_candidates, probe_provider_info, BackendProviderInfo,
};

#[tauri::command]
pub async fn discover_backend_providers() -> Result<Vec<BackendProviderInfo>, String> {
Expand Down Expand Up @@ -32,15 +34,7 @@ pub async fn probe_backend_provider(binary_path: String) -> Result<serde_json::V
"binary '{binary_path}' is not a discovered buzz-backend-* provider"
));
}
// request_id is for provider-side logging — not validated in the response
// (stdin→stdout is 1:1 per process invocation).
let request = serde_json::json!({
"op": "info",
"request_id": uuid::Uuid::new_v4().to_string(),
});
tokio::task::spawn_blocking(move || {
invoke_provider(&canonical, &request, std::time::Duration::from_secs(10))
})
.await
.map_err(|e| format!("spawn_blocking failed: {e}"))?
tokio::task::spawn_blocking(move || probe_provider_info(&canonical))
.await
.map_err(|e| format!("spawn_blocking failed: {e}"))?
}
Loading
Loading