[WIP] feat(spawner): server-hosted agents via buzz-spawner daemon + server-aware agent editing - #3113
Open
siddhartpai wants to merge 40 commits into
Open
[WIP] feat(spawner): server-hosted agents via buzz-spawner daemon + server-aware agent editing#3113siddhartpai wants to merge 40 commits into
siddhartpai wants to merge 40 commits into
Conversation
buzz-spawner daemon runs agents in containers, attested via the NIP-AS kind:24201 encrypted handshake. Agents can relocate from the desktop to a spawner keeping their pubkey, channels, profile, and NIP-AE memory. Relocation is persisted as state, not just performed as an action: the hand-off marks relocated_to_spawner on the ManagedAgentRecord (publish -> mark -> stop), and every local start path — manual start, app-launch restore, runtime reconcile, lazy @mention wake, and the auto-restart policy — refuses a relocated identity. Without this the auto-restart loop resurrected the local copy after hand-off, running one identity in two places (duplicate replies, double-billed turns). Mentions of a relocated agent skip the local wake entirely; the server copy sees them through the relay. The Agents screen shows a server badge instead of Start for relocated agents. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…al::hash Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ames Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…date send path Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…size guard Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A desktop prompt update carries only system prompt, model, and provider, so replacing the stored material wholesale wiped the agent's team instructions on the next container restart. Carry them forward in a new record field instead of merging them into the stored prompt: that field must stay byte-identical to the frame the owner sent, since its hash is what the status event echoes and what the client matches to confirm the update landed. Also warn on `PromptMaterial::hash` that the hash is published world-readable, so no credential field may ever join the struct. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The queue read storage at module import, when the cached relay origin is still null, so it loaded from a bare key while every write went to the origin-scoped one — pending updates never survived a restart. Hydrate lazily on first access instead, and make the community reset clear only in-memory state: persisting an empty map under the departing relay's key deleted that community's pending edits. Ack by the agent pubkey the spawner reports, with the slug only as a fallback for a status that has none. The slug a client queues under can drift from the spawner's (name-derived fallback, or a rename), and matching on it alone meant an ack that never arrived — a resend and a container restart every three minutes, forever. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An all-blank edit was still queued, but the spawner drops empty material without storing or acking it, so the entry stayed pending and was resent forever. Skip it at the push site. The pending chip also claimed the server was offline during the normal awaiting-ack window; it now says that only when the send itself failed, and it renders for any agent row with a pending update rather than relocated ones alone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…t a resolved relay origin The community reset nulls the cached relay origin right after resetting this queue, and it only refreshes asynchronously — so any access in that window hydrated from the shared empty-origin key and latched hydration for the whole session. The new community's persisted entries never loaded, and the next write pushed that wrong-key map onto the real one. Skip hydration (without latching) and skip persisting while the origin is unknown, merge rather than replace when it finally resolves so an edit queued in the meantime survives, and hydrate on the origin subscription instead of waiting for the next access. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… agents A spawner-hosted agent authenticates on the server (e.g. OAuth), but both edit dialogs still ran local model discovery and gated Save on local provider credentials, so editing such an agent demanded an ANTHROPIC_API_KEY this machine never uses and surfaced a spurious "Enter an Anthropic API key" warning. - usePersonaModelDiscovery: new serverManaged flag skips local discovery and the "Runtime not available" warning for server-hosted agents; the model catalog already comes from the spawner announcement. - localModeGateSatisfiedForSubmit: definition-dialog Save waives local credential env keys when server-managed (provider/model still required). - computeEditAgentFormValidity: instance-dialog Save ignores requiredEnvKeyMissing when server-managed. Mirrors the existing Databricks OAuth precedent of not requiring a typed credential when auth happens elsewhere. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ial status Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… host-global keys Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… updates with ack and restart Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ential badge Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…in fixtures) Post-rebase integration: main's build_managed_agent_summary gained a GlobalAgentConfig parameter and two new test fixtures construct ManagedAgentRecord, which now carries relocated_to_spawner. Also drop the runtimeAvailabilityWarning import left unused by the server-aware Edit dialog. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: sid <siddhartpai@gmail.com>
siddhartpai
force-pushed
the
server-agent-editing
branch
from
July 27, 2026 18:09
0b5a872 to
387ac5b
Compare
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: sid <siddhartpai@gmail.com>
…e build Lead with the one rule that explains most failures (every client must use the same public hostname, since the relay derives the tenant from the Host header), document the silent cross-host spawner failure mode, explain BUZZ_SPAWNER_AGENT_RELAY_URL constraints inside containers, and show how to build a local buzz-acp image instead of pulling. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: sid <siddhartpai@gmail.com>
…wner identity Container names are owner+slug scoped, but list() only sees containers labelled with the current spawner pubkey. A container created by a previous identity (wiped state dir, re-minted nsec) is therefore invisible to reconcile while still holding the name, and every create for that slug fails with Docker 409 forever. On a 409, inspect the squatter: if it carries the com.buzz.agent label it is spawner-managed (whichever identity created it) — remove it and retry the create once. Unlabelled containers are refused, so an operator's unrelated container of the same name is never touched. The workspace volume is a named mount and survives. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: sid <siddhartpai@gmail.com>
Author
|
This branch has been split into a reviewable 4-PR stack: #3185 (spawner foundation, ready for review) → #3186 (prompt/model editing) → #3187 (per-owner credentials) → #3188 (docs & deploy). Same content, re-signed and rebased onto current main. Review #3185 first; the drafts shrink to their own commits as predecessors merge. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the first-party server-hosted agent path proposed in #2859, plus the desktop editing flow for those agents:
crates/buzz-spawnerdaemon — watches the relay forkind:30178agent specs and reconciles them into Docker-isolatedbuzz-acpcontainers, with30179status events and the24201NIP-44 attestation handshake (nsec minted on the server, owner signs the NIP-OA tag from the client). Ships withDockerfile.spawner,Dockerfile.acp, and adeploy/composeservice.buzz-core/src/kind.rs, ingest/req gates, and the owner-delegated persona read-gate.buzz-sdk/src/spawner.rs— typed builders/parsers for the spawner event family (opt-in projection: specs cannot carry an nsec, auth tag, or env blob).Related issue
Implements #2859. Also addresses the local-API-key requirement aspect of #3034. (Searched existing issues/PRs — no duplicate PR found.)
Testing
crates/buzz-spawner/examples/owner_sim.rs— end-to-end owner simulation exercising spec publish, attestation handshake, prompt-update push, and ack against a live relay.fc6e0d50), plus unit tests around the prompt-update queue hydration/persistence rules.🤖 Generated with Claude Code