block / block/buzz

agents draft-create: let a create draft carry proposed config, surfaced visibly in the owner review form

Open
#4,109 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
32.7k
Forks
4.3k
Avg merge
1d 13h
Merged PRs (30d)
253

Description

`agents draft-update` accepts `--runtime --provider --model --respond-to`, but `agents draft-create` accepts only name + prompt, and the desktop's parser hard-rejects any extra key on create (`agentManagement.ts`, with the explicit test "chat creation cannot choose runtime, provider, model, or access").

We read that restriction as deliberate and think it's right as far as it goes: create drafts arrive from agent identities, and an agent shouldn't be able to propose itself `respond_to: anyone` plus an elevated runtime into a review form whose advanced section renders collapsed.

The cost is that legitimate programmatic team creation (we build agent packs that draft whole squads via the CLI) can't carry config at all — every created agent lands with defaults, and the owner reconfigures each one after the fact.

**Proposal: keep the gate, move it to visibility.** Accept the same optional quad the update path already accepts, and render any proposed values *expanded and highlighted* in the owner review form — never collapsed, ideally visually distinct from owner-chosen values — so the owner explicitly sees what the agent asked for before saving. The security property becomes "the owner always sees proposed config" instead of "proposed config is impossible".

The transport side is small and we've drafted it (CLI flags + serde fields + validation + tests, byte-compatible when the fields are unset so existing event hashes are untouched); the real work is the form UX, which we didn't want to presume. Reference diff below — happy to turn it into a PR if the direction is acceptable.

Reference diff: CLI transport side (~185 lines)

```diff
diff --git a/crates/buzz-cli/src/agent_management.rs b/crates/buzz-cli/src/agent_management.rs
index ce4059f..db22ad6 100644
--- a/crates/buzz-cli/src/agent_management.rs
+++ b/crates/buzz-cli/src/agent_management.rs
@@ -16,6 +16,14 @@ pub struct CreateAgentDraft {
pub channel_id: String,
pub display_name: String,
pub system_prompt: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub runtime: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub provider: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub model: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub respond_to: Option,
}

#[derive(Debug, Clone, Serialize)]
@@ -84,6 +92,19 @@ fn optional(value: Option, label: &str) -> Result, CliErr
value.map(|value| required(value, label, 300)).transpose()
}

+fn validated_respond_to(value: Option) -> Result, CliError> {
+ let respond_to = optional(value, "respond-to")?;
+ if respond_to
+ .as_deref()
+ .is_some_and(|value| value != "owner-only" && value != "anyone")
+ {
+ return Err(CliError::Usage(
+ "respond-to must be owner-only or anyone".into(),
+ ));
+ }
+ Ok(respond_to)
+}
+
fn build(
keys: &Keys,
owner: &PublicKey,
@@ -137,6 +158,10 @@ pub fn build_create(
channel_id: channel_id.clone(),
display_name: required(draft.display_name, "display name", MAX_NAME_CHARS)?,
system_prompt: required(draft.system_prompt, "system prompt", MAX_PROMPT_CHARS)?,
+ runtime: optional(draft.runtime, "runtime")?,
+ provider: optional(draft.provider, "provider")?,
+ model: optional(draft.model, "model")?,
+ respond_to: validated_respond_to(draft.respond_to)?,
};
build(keys, owner, channel_id, "create", request)
}
@@ -149,15 +174,7 @@ pub fn build_update(
let channel_id = required(draft.channel_id, "channel", 128)?;
uuid::Uuid::parse_str(&channel_id)
.map_err(|_| CliError::Usage(format!("invalid channel UUID: {channel_id}")))?;
- let respond_to = optional(draft.respond_to, "respond-to")?;
- if respond_to
- .as_deref()
- .is_some_and(|value| value != "owner-only" && value != "anyone")
- {
- return Err(CliError::Usage(
- "respond-to must be owner-only or anyone".into(),
- ));
- }
+ let respond_to = validated_respond_to(draft.respond_to)?;
let request = UpdateAgentDraft {
channel_id: channel_id.clone(),
agent_name: required(draft.agent_name, "agent name", MAX_NAME_CHARS)?,
@@ -203,6 +220,10 @@ mod tests {
channel_id: CHANNEL.into(),
display_name: "Research helper".into(),
system_prompt: "Find sources.".into(),
+ runtime: None,
+ provider: None,
+ model: None,
+ respond_to: None,
},
)
.unwrap();
@@ -240,6 +261,50 @@ mod tests {
assert!(payload["payload"]["request"].get("respondTo").is_none());
}

+ #[test]
+ fn create_carries_optional_config_in_camel_case() {
+ let owner = Keys::generate();
+ let built = build_create(
+ &Keys::generate(),
+ &owner.public_key(),
+ CreateAgentDraft {
+ channel_id: CHANNEL.into(),
+ display_name: "Research helper".into(),
+ system_prompt: "Find sources.".into(),
+ runtime: Some("claude".into()),
+ provider: Some("anthropic".into()),
+ model: Some("opus".into()),
+ respond_to: Some("anyone".into()),
+ },
+ )
+ .unwrap();
+ let payload: serde_json::Value = decrypt_observer_payload(&owner, &built.event).unwrap();
+ let request = &payload["payload"]["request"];
+ assert_eq!(request["runtime"], "claude");
+ assert_eq!(request["provider"], "anthropic");
+ assert_eq!(request["model"], "opus");
+ assert_eq!(request["respondTo"], "anyone");
+ }
+
+ #[test]
+ fn create_rejects_unknown_respond_to() {
+ let error = build_create(
+ &Keys::generate(),
+ &Keys::generate().public_key(),
+ CreateAgentDraft {
+ channel_id: CHANNEL.into(),
+ display_name: "Scout".into(),
+ system_prompt: "Help".into(),
+ runtime: None,
+ provider: None,
+ model: None,
+ respond_to: Some("everyone".into()),
+ },
+ )
+ .unwrap_err();
+ assert!(error.to_string().contains("respond-to must be"));
+ }
+
#[test]
fn update_requires_a_change() {
let error = build_update(
@@ -269,6 +334,10 @@ mod tests {
channel_id: "general".into(),
display_name: "Scout".into(),
system_prompt: "Help".into(),
+ runtime: None,
+ provider: None,
+ model: None,
+ respond_to: None,
},
)
.unwrap_err();
diff --git a/crates/buzz-cli/src/commands/agents.rs b/crates/buzz-cli/src/commands/agents.rs
index 58564a4..da5082d 100644
--- a/crates/buzz-cli/src/commands/agents.rs
+++ b/crates/buzz-cli/src/commands/agents.rs
@@ -15,6 +15,10 @@ pub async fn dispatch(command: AgentsCmd, client: &BuzzClient) -> Result<(), Cli
channel,
display_name,
system_prompt,
+ runtime,
+ provider,
+ model,
+ respond_to,
} => {
let owner = require_owner(client)?;
let built = build_create(
@@ -24,6 +28,10 @@ pub async fn dispatch(command: AgentsCmd, client: &BuzzClient) -> Result<(), Cli
channel_id: channel,
display_name,
system_prompt: read_or_stdin(&system_prompt)?,
+ runtime,
+ provider,
+ model,
+ respond_to: respond_to.map(RespondToArg::to_wire),
},
)?;
let response = client.publish_ephemeral_event(built.event).await?;
diff --git a/crates/buzz-cli/src/lib.rs b/crates/buzz-cli/src/lib.rs
index 0b46734..e028d5d 100644
--- a/crates/buzz-cli/src/lib.rs
+++ b/crates/buzz-cli/src/lib.rs
@@ -269,6 +269,14 @@ pub enum AgentsCmd {
/// Proposed instructions; use '-' to read from stdin
#[arg(long)]
system_prompt: String,
+ #[arg(long)]
+ runtime: Option,
+ #[arg(long)]
+ provider: Option,
+ #[arg(long)]
+ model: Option,
+ #[arg(long, value_enum)]
+ respond_to: Option,
},
/// Open a prefilled edit-agent form in the owner's Buzz Desktop
DraftUpdate {
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Contributor guide

Open the contributing guide

Research direction

Start with the create path in crates/buzz-cli/src/agent_management.rs, crates/buzz-cli/src/commands/agents.rs, and crates/buzz-cli/src/lib.rs, then inspect the desktop parser and review form in agentManagement.ts. Use the existing “chat creation cannot choose runtime, provider, model, or access” test as the current behavior reference. Done means optional proposed values reach creation and are visibly expanded and highlighted in the owner review form, with unset fields remaining byte-compatible.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust, typescript
Domain
cli, desktop
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.