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
2 changes: 1 addition & 1 deletion crates/agent-controller/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Native agent identities

The desktop host uses this crate for Agents2's `app_agent_*` commands: identity
The desktop host uses this crate for Agents' `app_agent_*` commands: identity
custody, signing, credentials and bringing over identities from earlier stores.
Plugin runtimes own agent execution; this crate no longer contains a controller,
supervisor, harness launcher, teams, provider defaults or bundled runtime loader.
Expand Down
2 changes: 1 addition & 1 deletion crates/agent-controller/src/app_agent.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Identities for agents the app runs through a plugin (Agents2). They are not
//! Identities for agents the app runs through a plugin (Agents). They are not
//! harness agents: there is no process, harness setting or controller record.
//! Native keeps each key and its owner attestation and signs only bounded kinds,
//! bounded requests to its community, and its memory.
Expand Down
8 changes: 4 additions & 4 deletions crates/agent-controller/src/app_agent/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ fn owner() -> String {
auth[1].clone()
}
fn created(dir: &std::path::Path, credentials: &Memory) -> (AppAgents, AppAgent) {
let agents = AppAgents::open(dir.join("agents2/identities.json"));
let agents = AppAgents::open(dir.join("agents/identities.json"));
let prepared = AppAgents::prepare("https://relay.example/", &owner(), "p/t", "Ada").unwrap();
let auth: Vec<String> =
serde_json::from_str(&crate::secret::test_attestation(prepared.pubkey())).unwrap();
Expand Down Expand Up @@ -319,7 +319,7 @@ fn signs_uploads_and_memory_for_its_own_community_and_owner() {
#[test]
fn concurrent_writers_on_one_file_keep_every_identity() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("agents2/identities.json");
let path = dir.path().join("agents/identities.json");
let credentials = std::sync::Arc::new(Memory::default());
let threads: Vec<_> = (0..8)
.map(|_| {
Expand Down Expand Up @@ -365,7 +365,7 @@ fn an_adopted_key_keeps_its_identity_picture_and_shared_saved_key() {
credentials.add(&id, &key).unwrap();
set(&credentials.1.add, true);
let dirs: Vec<_> = (0..3).map(|_| tempfile::tempdir().unwrap()).collect();
let store = |n: usize| AppAgents::open(dirs[n].path().join("agents2/identities.json"));
let store = |n: usize| AppAgents::open(dirs[n].path().join("agents/identities.json"));
let adopt = |picture: &str| {
AppAgents::adopt(
"https://relay.example/",
Expand Down Expand Up @@ -430,7 +430,7 @@ fn an_adopted_key_keeps_its_identity_picture_and_shared_saved_key() {
fn an_adopted_key_that_cannot_be_saved_withdraws_only_its_row() {
let dir = tempfile::tempdir().unwrap();
let credentials = Memory::default();
let agents = AppAgents::open(dir.path().join("agents2/identities.json"));
let agents = AppAgents::open(dir.path().join("agents/identities.json"));
let key = Secret::generate().unwrap();
let pubkey = key.pubkey().to_owned();
let auth: Vec<String> =
Expand Down
2 changes: 1 addition & 1 deletion crates/agent-controller/src/port.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Agent identities an earlier agent system saved on this machine, which Agents2
//! Agent identities an earlier agent system saved on this machine, which Agents
//! can take over: the harness controller's library and old Buzz's. Only the
//! identity and its public name, picture and description move; instructions,
//! harness settings and memberships stay behind. Neither store is ever written.
Expand Down
6 changes: 3 additions & 3 deletions crates/agent-controller/src/port/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ fn takes_the_key_from_the_store_it_was_listed_in() {
agent.about,
)
.unwrap();
let destination = crate::AppAgents::open(dir.path().join("agents2/identities.json"));
let destination = crate::AppAgents::open(dir.path().join("agents/identities.json"));
let (agent, key) = destination.commit(prepared, &auth, &credentials).unwrap();
let reopened = destination.get(&agent.pubkey).unwrap();
let profile = reopened.profile_event(&key).unwrap();
Expand Down Expand Up @@ -271,7 +271,7 @@ fn imports_authored_descriptions_from_the_saved_profile_or_exact_definition() {
}

#[test]
fn retained_harness_artwork_survives_agents2_storage_and_signed_publication() {
fn retained_harness_artwork_survives_agents_storage_and_signed_publication() {
let retained = json!({
"record": {"persona_id": "reviewer", "avatar_url": "https://old.example/identity.png"},
"definition": {"slug": "reviewer", "avatar_url": "https://old.example/definition.png"}
Expand Down Expand Up @@ -350,7 +350,7 @@ fn retained_harness_artwork_survives_agents2_storage_and_signed_publication() {
agent.about,
)
.unwrap();
let path = dir.path().join("agents2/identities.json");
let path = dir.path().join("agents/identities.json");
let (agent, _) = crate::AppAgents::open(path.clone())
.commit(prepared, &auth, &credentials)
.unwrap();
Expand Down
62 changes: 57 additions & 5 deletions crates/plugin-manager/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use std::{
};

pub mod imports;
mod migrate_agent_flag;

pub type Result<T> = std::result::Result<T, String>;
const LIMIT: u64 = 8 * 1024 * 1024;
Expand Down Expand Up @@ -212,8 +213,8 @@ pub fn bundled_manifests() -> Vec<Manifest> {
.expect("bestie manifest"),
serde_json::from_str(include_str!("../../../src/bundled/projects/manifest.json"))
.expect("projects manifest"),
serde_json::from_str(include_str!("../../../src/bundled/agents2/manifest.json"))
.expect("agents2 manifest"),
serde_json::from_str(include_str!("../../../src/bundled/agents/manifest.json"))
.expect("agents manifest"),
serde_json::from_str(include_str!(
"../../../src/bundled/claude-code/manifest.json"
))
Expand Down Expand Up @@ -258,7 +259,7 @@ fn enabled_by_default(id: &str) -> bool {
| "buzz.inbox"
| "buzz.reminders"
| "buzz.projects"
| "buzz.agents2"
| "buzz.agents"
| "buzz.claude-code"
| "buzz.codex"
| "buzz.workflows"
Expand Down Expand Up @@ -317,7 +318,7 @@ struct Registry {
impl Default for Registry {
fn default() -> Self {
Self {
version: 1,
version: 2,
bundled_enabled: true,
bundled_overrides: BTreeMap::new(),
installed: BTreeMap::new(),
Expand Down Expand Up @@ -512,7 +513,7 @@ impl Manager {
};
let text = read_file_limited(file)?;
let mut r: Registry = serde_json::from_str(&text).map_err(err)?;
if r.version != 1 {
if r.version != 1 && r.version != 2 {
return Err(
"Unsupported settings version; use a compatible Buzz or recover settings".into(),
);
Expand All @@ -536,6 +537,9 @@ impl Manager {
valid_hash(h)?;
}
}
if migrate_agent_flag::migrate(&mut r) {
self.save(&r)?;
}
Ok(r)
}
fn save(&self, r: &Registry) -> Result<()> {
Expand Down Expand Up @@ -1560,6 +1564,54 @@ mod tests {
assert!(!enabled(&manager), "{id} exposes persisted disable");
}

#[test]
fn agents_migrates_its_switch_and_ignores_v1s() {
let open = |overrides: serde_json::Value| {
let temp = tempfile::tempdir().unwrap();
let profile = temp.path().join("profiles").join("agents");
fs::create_dir_all(&profile).unwrap();
let registry = serde_json::json!({
"version": 1,
"bundledEnabled": true,
"bundledOverrides": overrides,
"installed": {},
});
fs::write(profile.join("registry.json"), registry.to_string()).unwrap();
let manager = Manager::open(Some(temp.path().into()), "agents", false).unwrap();
(temp, manager)
};
let enabled = |manager: &Manager| {
manager
.catalog()
.unwrap()
.plugins
.into_iter()
.find(|plugin| plugin.manifest.id == "buzz.agents")
.unwrap()
.enabled
};
let (_v1, manager) = open(serde_json::json!({ "buzz.agents": false }));
assert!(enabled(&manager), "v1's switch does not turn Agents off");
let (_temp, manager) = open(serde_json::json!({ "buzz.agents2": false }));
assert!(!enabled(&manager), "the Agents2 switch still applies");
manager.change("enable", "buzz.agents").unwrap();
assert!(enabled(&manager));
let reopened = Manager::open(Some(_temp.path().into()), "agents", false).unwrap();
assert!(enabled(&reopened), "reopening keeps the new preference");
reopened.change("disable", "buzz.agents").unwrap();
assert!(!enabled(&manager), "migration does not reset later changes");
reopened.change("enable", "buzz.agents").unwrap();
let saved: serde_json::Value = serde_json::from_slice(
&fs::read(manager.storage_root().join("registry.json")).unwrap(),
)
.unwrap();
assert_eq!(
saved["bundledOverrides"],
serde_json::json!({ "buzz.agents": true }),
"the switch now uses the current plugin id"
);
}

#[test]
fn formerly_installed_agent_plugins_upgrade_to_the_bundled_copy() {
let manifest: serde_json::Value = serde_json::from_str(include_str!(
Expand Down
17 changes: 17 additions & 0 deletions crates/plugin-manager/src/migrate_agent_flag.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//! TEMPORARY agents rename: remove after 2026-10-18 with the version-1 reader.
//! Version 2 distinguishes new Agents preferences from the retired v1 plugin.
use super::Registry;

pub(super) fn migrate(registry: &mut Registry) -> bool {
if registry.version != 1 {
return false;
}
registry.bundled_overrides.remove("buzz.agents");
if let Some(enabled) = registry.bundled_overrides.remove("buzz.agents2") {
registry
.bundled_overrides
.insert("buzz.agents".into(), enabled);
}
registry.version = 2;
true
}
39 changes: 30 additions & 9 deletions docs/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,35 @@

## Scope

Agents2 (`src/bundled/agents2`, `src/features/agents2`) is the app's only agent
Agents (`src/bundled/agents`, `src/features/agents`) is the app's only agent
system: it creates, runs and deletes the viewer's agents. This document covers
what the rest of the app knows about agents: shared selection, mentions, archive
display, activity, names, inventory and usage. The v1 Agents page, Profiles plugin,
Channel templates plugin, agent snapshots, team bundles, community catalog and
agent-management request popups are gone, as are the native `agent_control_*`,
model-discovery and harness-install commands behind them.

Agents was called Agents2 while v1 still shipped. Startup now moves its identity
folder to `agents/identities.json` and its browser settings to `buzz.agents.v1`
and `buzz.agents.teams.v1`. Identity bytes and keychain entries are unchanged.
The Agents2 plugin switch moves to `buzz.agents`; the retired v1 switch is
removed. Plugin registry versions distinguish these preferences (native v2,
browser v3), so a later restart preserves changes to the new switch.

The temporary migrators are `src/features/agents/migrate-storage.ts`,
`src/plugins/migrate-agent-flag.ts`,
`crates/plugin-manager/src/migrate_agent_flag.rs`, and
`src-tauri/src/sign_out/migrate_agents.rs`. Remove them and their call sites after
2026-10-18 once development users have upgraded; remove the old registry-version
readers and the legacy sign-out keep/key-deletion entries at the same time.
This is a one-way development migration: close older Buzz copies before the
first upgraded launch and use the upgraded build afterward. Native migration
requires the existing exclusive instance lock; conflicts stop startup without
replacing either folder. A pending erase handles both folder names before the
migration runs. Browser moves write the destination before removing the source
and resume identical interrupted copies; differing destinations are left intact
and reported as migration errors.

Agent identity is evidence, never authority. Names, library rows, local agent rows,
archive state and presence do not prove ownership, membership or runtime state.

Expand All @@ -18,13 +39,13 @@ archive state and presence do not prove ownership, membership or runtime state.
`LocalAgents` (`src/features/agents/local.ts`) is a read-only snapshot of the
viewer's live agents on this machine, in any community:
`{ status, agents: [{ pubkey, name, relayUrl, owner }] }`, with `status` one of
`unavailable`, `loading`, `ready` or `error`. `Agents2Service.local()` produces it
from its saved identities (deleted ones omitted), so it carries Agents2's own
loading and error state. Agents2's `snapshot()` is different: it holds only the
`unavailable`, `loading`, `ready` or `error`. `AgentsService.local()` produces it
from its saved identities (deleted ones omitted), so it carries Agents' own
loading and error state. Agents' `snapshot()` is different: it holds only the
selected community's agents, with their settings.

`app/services.ts` composes consumers before Agents2 exists, through
`createLocalAgents()`. Until `bind` connects `agents2.local`, the snapshot is
`app/services.ts` composes consumers before Agents exists, through
`createLocalAgents()`. Until `bind` connects `agents.local`, the snapshot is
`unavailable`; unbinding returns it there. Two consumers read it directly:

- the identity-name service, which lets a local agent's name win in its own
Expand Down Expand Up @@ -65,7 +86,7 @@ retirement revokes its candidates.

`templates` serves saved channel-kit templates and section defaults (session setup
resolves lineups through it with `templateAgentChoices`). Its identities are the
local agents once Agents2 is bound, and the library only while the local source is
local agents once Agents is bound, and the library only while the local source is
`unavailable`. It never falls back to the library after a local error.
`templateAgentChoices` returns nothing until archive evidence is ready, and
library-only choices additionally need visible community membership. Saved keys
Expand Down Expand Up @@ -314,8 +335,8 @@ the relay re-verifies consent. An owner request copies the target's single live
by that re-read, and only a definitive relay rejection skips it. No profile pane
offers Archive or Unarchive now; the only caller is agent deletion.

**Delete agent** on Agents2 forgets the agent's key on this device, then
(`Agents2Service.cleanup`, shared helpers in `features/agents/relay-removal.ts`)
**Delete agent** on Agents forgets the agent's key on this device, then
(`AgentsService.cleanup`, shared helpers in `features/agents/relay-removal.ts`)
runs whenever its community is open:

1. An exact one-member 9001 (`h`, `p`, `client-id`) goes through the outbox for every
Expand Down
2 changes: 1 addition & 1 deletion docs/channels.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ The **+** action in the DMs sidebar header opens **New message**, a routed empty
conversation in Messages. The header remains available before the first DM exists. Its inline **To:**
field owns a paginated people picker and up to eight recipient chips, excluding
this viewer. Agent profiles are offered only when their exact public key is one of the viewer's
local agents (Agents2) for this community, the `managed` choices of
local agents (Agents) for this community, the `managed` choices of
`session.agentChoices`. Public profile hints and the inventory library do not
qualify; this filter also applies to searches and cached results. Selected agent
chips are revalidated against the current local agents before a fresh open and
Expand Down
4 changes: 2 additions & 2 deletions docs/codex-binding-readiness.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Codex binding readiness

> **Status: superseded by Agents2.** This describes the v1 native Codex binding
> **Status: superseded by Agents.** This describes the v1 native Codex binding
> (Settings rows, Create/Edit form, native model discovery and harness install),
> which was removed with the v1 Agents system. Codex now reaches the app as an
> Agents2 agent type registered by the bundled Codex plugin.
> The agent type registered by the bundled Codex plugin.

Codex is a native integration. New selection requires installed tools; Create
and Save require no inference check. Settings shows tool presence and, once
Expand Down
4 changes: 2 additions & 2 deletions docs/codex-create-edit-ui.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Codex Create and Edit acceptance

> **Status: superseded by Agents2.** This describes the v1 native Codex binding
> **Status: superseded by Agents.** This describes the v1 native Codex binding
> (Settings rows, Create/Edit form, native model discovery and harness install),
> which was removed with the v1 Agents system. Codex now reaches the app as an
> Agents2 agent type registered by the bundled Codex plugin.
> The agent type registered by the bundled Codex plugin.

This is the current contract after the October 8, 2026 simplification. Historical
validation/proof test results in earlier stack revisions do not establish this
Expand Down
4 changes: 2 additions & 2 deletions docs/codex-model-discovery.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Codex model and effort discovery

> **Status: superseded by Agents2.** This describes the v1 native Codex binding
> **Status: superseded by Agents.** This describes the v1 native Codex binding
> (Settings rows, Create/Edit form, native model discovery and harness install),
> which was removed with the v1 Agents system. Codex now reaches the app as an
> Agents2 agent type registered by the bundled Codex plugin.
> The agent type registered by the bundled Codex plugin.

Create and Edit use headless model and effort discovery for the native Codex
binding described in [Codex binding readiness](codex-binding-readiness.md).
Expand Down
4 changes: 2 additions & 2 deletions docs/codex-validation-execution.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Codex persistence and execution

> **Status: superseded by Agents2.** This describes the v1 native Codex binding
> **Status: superseded by Agents.** This describes the v1 native Codex binding
> (Settings rows, Create/Edit form, native model discovery and harness install),
> which was removed with the v1 Agents system. Codex now reaches the app as an
> Agents2 agent type registered by the bundled Codex plugin.
> The agent type registered by the bundled Codex plugin.

The October 8, 2026 simplification replaces the earlier mandatory connection
validation contract. Create and Save follow the existing Claude lifecycle:
Expand Down
2 changes: 1 addition & 1 deletion docs/communities.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ community origin and viewer; channel IDs alone are not sufficient keys.

Selecting a saved membership lazily acquires its session. Sessions survive page
navigation and switching; startup opens only the selected one. Once its agent
list loads, Agents2 also opens each joined community where the viewer has an
list loads, Agents also opens each joined community where the viewer has an
agent, through `open`, which connects without selecting so those agents keep
running, and retries one whose connection failed while it is not selected. Opened communities
retain their existing per-session query/cache budgets. Arbitrary destinations mean
Expand Down
Loading
Loading