Skip to content
Closed
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
22 changes: 21 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ buzz-core (zero I/O — types, verification, filter matching, kind registry)
├── buzz-pubsub (Redis pub/sub, presence, typing indicators)
├── buzz-search (Postgres FTS: query, delete)
├── buzz-audit (hash-chain tamper-evident log)
└── buzz-workflow (YAML-as-code automation engine)
├── buzz-workflow (YAML-as-code automation engine)
├── buzz-flow (Flow Studio — canvas blocks, KB, tables, files; kinds 46200–46399)
└── buzz-agent-studio (Agent Studio — graph, skills, telemetry; kinds 47200–47399)
└── buzz-relay (ties everything together — the server)

Expand Down Expand Up @@ -506,6 +508,24 @@ Tamper-evident append-only log with SHA-256 hash chaining.

---

### buzz-flow — Flow Studio (Buzz Hive)

Visual workflow canvas, block/tool registry, knowledge base, tables, and file metadata. All writes are Nostr events (kinds 46200–46399); Postgres tables in migration `0032` are a read-model projector target.

**HTTP surface (relay):** `/flow-studio/blocks`, `/flow-studio/graph`, `/flow-studio/knowledge/search`, `/flow-studio/tables/{id}/rows`, `/flow-studio/files`, YAML export from canvas.

**Projector:** `buzz-flow/src/projector.rs` — ingest hook in `buzz-relay` applies KB docs, table rows, and file metadata to Postgres.

---

### buzz-agent-studio — Agent Studio (Buzz Hive)

Agent/skill dependency graph, GitHub skill import planning, and ACP session telemetry (kind 47300). Persona publishes also emit kind 47200/47201.

**HTTP surface:** `/agent-studio/graph`, `/agent-studio/sessions`, `/agent-studio/costs`, `/agent-studio/skills/import`.

---

### buzz-workflow — YAML-as-Code Automation Engine

Parses, validates, and executes channel-scoped workflow definitions. In multi-community mode workflow definitions, runs, approvals, webhook routes, and schedules inherit the host-derived community and evaluate triggers only against events in that community.
Expand Down
25 changes: 25 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ members = [
"crates/buzz-admin",
"crates/buzz-deletion",
"crates/buzz-workflow",
"crates/buzz-flow",
"crates/buzz-agent-studio",
"crates/buzz-media",
"crates/buzz-cli",
"crates/buzz-pairing-cli",
Expand Down Expand Up @@ -142,6 +144,8 @@ buzz-pubsub = { path = "crates/buzz-pubsub" }
buzz-search = { path = "crates/buzz-search" }
buzz-audit = { path = "crates/buzz-audit" }
buzz-workflow = { path = "crates/buzz-workflow" }
buzz-flow = { path = "crates/buzz-flow" }
buzz-agent-studio = { path = "crates/buzz-agent-studio" }
buzz-media = { path = "crates/buzz-media" }
buzz-sdk = { path = "crates/buzz-sdk" }
buzz-ws-client = { path = "crates/buzz-ws-client" }
Expand Down
22 changes: 22 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Buzz
Copyright contributors to the Buzz project.

Licensed under the Apache License, Version 2.0.

---

## Third-party components (Buzz Hive ports)

### claude-code-cli-ui (Agent Studio)

- Upstream: https://github.com/Ngxba/claude-code-cli-ui
- License: MIT
- Ported to: `crates/buzz-agent-studio`, `desktop/src/features/agent-studio`
- Copyright remains with original authors; see upstream LICENSE.

### Sim (Flow Studio)

- Upstream: https://github.com/simstudioai/sim
- License: Apache-2.0 (verify upstream LICENSE before release)
- Ported to: `crates/buzz-flow`, `desktop/src/features/flow-studio`
- Reference only — not shipped as Next.js runtime.
15 changes: 14 additions & 1 deletion VISION.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ One community is your entire workspace. Work, conversation, agents, automation,
| ✉️ **DMs** | 1:1 and group. Up to 9. | URGENT only |
| 🤖 **Agents** | Directory. Your agents. Job board. | — |
| ⚡ **Workflows** | YAML-as-code automation. Traces. | Approvals only |
| 🧩 **Flow Studio** | Visual workflow canvas (Buzz Hive). | Approvals only |
| 🤖 **Agent Studio** | Agent graph, skill import, cost monitor (Buzz Hive). | — |
| 🔍 **Search** | Cmd+K. Instant. Full-text. | — |

*Desktop app supports all seven surfaces today.*
Expand Down Expand Up @@ -122,7 +124,18 @@ Relay communities can pool opted-in member hardware into shared AI compute. Exis

Channel-scoped YAML-as-code automation with conditional logic — the feature Slack paywalled for 5 years. Message triggers, reaction triggers, scheduled runs, webhooks. Every step traced. Agents manage workflows through MCP tools.

Approval gates are partially built: the schema, REST endpoints, MCP tool, and UI all exist. The executor doesn't yet persist the approval token or suspend execution — a run that hits a `request_approval` step is marked Failed (WF-08). The infrastructure is there; the wiring is next.
Approval gates suspend execution at `request_approval` steps, persist an approval token, and resume after grant — wired end-to-end for Flow Studio human-approval blocks (WF-08).

---

## Buzz Hive (Flow Studio + Agent Studio)

Visual workflow builder and agent tooling on the same Nostr event log as everything else:

- **Flow Studio** — canvas blocks → YAML → `buzz-workflow` runs; knowledge base (pgvector), tables, and files projected to Postgres; block execution emits kind 46201 for cost rollup.
- **Agent Studio** — persona/skill dependency graph, GitHub skill import, unified session cost monitor (ACP telemetry kind 47300 + flow blocks).

Both surfaces are preview features in the desktop app (`flow-studio`, `agent-studio`). Spec: `docs/BUZZ_HIVE_MERGE_SPEC.md`.

---

Expand Down
1 change: 1 addition & 0 deletions crates/buzz-acp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ path = "src/main.rs"
# Internal
buzz-core = { workspace = true }
buzz-sdk = { workspace = true }
buzz-agent-studio = { workspace = true }
buzz-persona = { path = "../buzz-persona" }

# Nostr
Expand Down
72 changes: 72 additions & 0 deletions crates/buzz-acp/src/pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4177,6 +4177,78 @@ async fn publish_agent_turn_metric(
"NIP-AM: publish timed out"
),
}
publish_agent_session_telemetry(ctx, &usage, session_id).await;
}

/// Best-effort: publish kind 47300 session telemetry for Agent Studio monitor.
async fn publish_agent_session_telemetry(
ctx: &PromptContext,
usage: &crate::usage::TurnUsage,
session_id: &str,
) {
use buzz_agent_studio::events::AgentSessionTelemetry;
use nostr::{EventBuilder, Kind, Tag};

let Some(owner_pk) = ctx.agent_owner_pubkey.as_ref() else {
return;
};

let payload = AgentSessionTelemetry {
session_id: session_id.to_string(),
agent_id: Some(ctx.harness_name.clone()),
input_tokens: usage.cumulative_input_tokens.unwrap_or(0),
output_tokens: usage.cumulative_output_tokens.unwrap_or(0),
cost_usd: usage.cumulative_cost_usd.unwrap_or(0.0),
tool_calls: 0,
};
let content = match serde_json::to_string(&payload) {
Ok(content) => content,
Err(error) => {
tracing::warn!(
target: "pool::metrics",
session_id,
"Agent Studio telemetry serialize failed: {error}"
);
return;
}
};
let owner_hex = owner_pk.to_hex();
let agent_hex = ctx.agent_keys.public_key().to_hex();
let event = match EventBuilder::new(
Kind::Custom(buzz_core::kind::KIND_AGENT_SESSION_TELEMETRY as u16),
content,
)
.tags([
Tag::parse(["d", session_id]).expect("d tag"),
Tag::parse(["p", &owner_hex]).expect("p tag"),
Tag::parse(["agent", &agent_hex]).expect("agent tag"),
])
.sign_with_keys(&ctx.agent_keys)
{
Ok(event) => event,
Err(error) => {
tracing::warn!(
target: "pool::metrics",
session_id,
"Agent Studio telemetry sign failed: {error}"
);
return;
}
};
const TELEMETRY_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(3);
match tokio::time::timeout(TELEMETRY_TIMEOUT, ctx.rest_client.submit_event(&event)).await {
Ok(Ok(_)) => {}
Ok(Err(error)) => tracing::warn!(
target: "pool::metrics",
session_id,
"Agent Studio telemetry publish failed: {error}"
),
Err(_) => tracing::warn!(
target: "pool::metrics",
session_id,
"Agent Studio telemetry publish timed out"
),
}
}

const REACTION_SEEN: &str = "👀";
Expand Down
14 changes: 14 additions & 0 deletions crates/buzz-agent-studio/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "buzz-agent-studio"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
description = "Buzz Agent Studio — agent/skill graph ported from claude-code-cli-ui (Buzz Hive P0 skeleton)"

[dependencies]
buzz-core = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
110 changes: 110 additions & 0 deletions crates/buzz-agent-studio/src/events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//! Nostr event kinds and payloads for Buzz Agent Studio (claude-code-cli-ui merge).
//!
//! Kind numbers are defined in `buzz_core::kind` (range 47200–47399).

pub use buzz_core::kind::{
is_agent_studio_kind, KIND_AGENT_CONFIG_CREATED, KIND_AGENT_CONFIG_UPDATED,
KIND_AGENT_GRAPH_EDGE, KIND_AGENT_SESSION_TELEMETRY, KIND_AGENT_SKILL_IMPORTED,
};

/// Payload for [`KIND_AGENT_CONFIG_CREATED`].
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct AgentConfigCreated {
/// Persona / agent slug (`d` tag).
pub agent_id: String,
/// Serialized persona frontmatter or JSON config.
pub config_json: String,
}

/// Payload for [`KIND_AGENT_CONFIG_UPDATED`].
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct AgentConfigUpdated {
/// Persona / agent slug (`d` tag).
pub agent_id: String,
/// Serialized persona frontmatter or JSON config.
pub config_json: String,
}

/// Payload for [`KIND_AGENT_SESSION_TELEMETRY`].
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct AgentSessionTelemetry {
/// Session identifier.
pub session_id: String,
/// Agent / persona slug when known.
pub agent_id: Option<String>,
/// Cumulative input tokens.
pub input_tokens: u64,
/// Cumulative output tokens.
pub output_tokens: u64,
/// Estimated USD cost.
pub cost_usd: f64,
/// Tool invocation count.
pub tool_calls: u32,
}

/// Payload for [`KIND_AGENT_GRAPH_EDGE`].
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct AgentGraphEdge {
/// Source node kind label.
pub source_type: String,
/// Source slug.
pub source_slug: String,
/// Target node kind label.
pub target_type: String,
/// Target slug.
pub target_slug: String,
/// Edge semantics.
pub relationship_type: String,
/// Detection evidence.
pub evidence: String,
}

/// Payload for [`KIND_AGENT_SKILL_IMPORTED`].
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct AgentSkillImported {
/// Skill identifier.
pub skill_id: String,
/// Source repository URL when imported from GitHub.
pub source_repo: Option<String>,
/// Commit SHA at import time.
pub source_commit: Option<String>,
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn agent_config_json_roundtrip() {
let event = AgentConfigCreated {
agent_id: "reviewer".into(),
config_json: r#"{"model":"claude-sonnet"}"#.into(),
};
let json = serde_json::to_string(&event).expect("serialize");
let back: AgentConfigCreated = serde_json::from_str(&json).expect("deserialize");
assert_eq!(back, event);
}

#[test]
fn skill_import_json_roundtrip() {
let event = AgentSkillImported {
skill_id: "lint-rust".into(),
source_repo: Some("https://github.com/example/skills".into()),
source_commit: Some("abc123".into()),
};
let json = serde_json::to_string(&event).expect("serialize");
let back: AgentSkillImported = serde_json::from_str(&json).expect("deserialize");
assert_eq!(back, event);
}

#[test]
fn kind_range_covers_agent_studio() {
assert!(is_agent_studio_kind(KIND_AGENT_CONFIG_CREATED));
assert!(is_agent_studio_kind(KIND_AGENT_GRAPH_EDGE));
assert!(is_agent_studio_kind(47399));
assert!(!is_agent_studio_kind(47199));
assert!(!is_agent_studio_kind(47400));
assert!(!is_agent_studio_kind(48001));
assert!(!is_agent_studio_kind(48100));
}
}
Loading