Skip to content
Draft
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
15 changes: 15 additions & 0 deletions crates/buzz-acp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,21 @@ All configuration is via environment variables (or CLI flags — every env var h

**Legacy env vars:** `BUZZ_ACP_PRIVATE_KEY`, `BUZZ_ACP_API_TOKEN`, and `BUZZ_ACP_TURN_TIMEOUT` (replaced by `BUZZ_ACP_IDLE_TIMEOUT`) are still accepted as fallbacks.

### Experimental information-flow audit

`--information-flow audit` (or `BUZZ_ACP_INFORMATION_FLOW=audit`) enables the
audience-scoped IFC prototype. It verifies trigger events and relay-signed channel
policy, derives `D = (Audience, Context, Epoch, Capabilities)`, evaluates read,
call, publish, and process-reuse rules, and keeps a conservative process-level
state label. Decisions are emitted under the `buzz_acp::ifc` tracing target.

The default is `off`. In that mode no IFC auditor is constructed, no extra channel
policy queries run, and prompt/session behavior is unchanged.

Audit mode is observational. It does not filter prompts, block tools, split agent
processes, bind replies to a destination, or provide OS confinement. Its logs call
those gaps out explicitly; enabling it is not an enforcement claim.

### Parallel Agents & Heartbeat

| Flag | Env Var | Default | Description |
Expand Down
56 changes: 55 additions & 1 deletion crates/buzz-acp/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,29 @@ pub enum PermissionMode {
Plan,
}

/// Information-flow mode for the experimental audience policy.
///
/// `Off` preserves the existing harness path without membership queries or policy
/// bookkeeping. `Audit` evaluates and logs the design-paper rules but does not alter
/// prompts, tools, session reuse, or publication.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, clap::ValueEnum)]
pub enum InformationFlowMode {
/// Do not construct or invoke the experimental policy evaluator.
#[default]
Off,
/// Evaluate and log policy without changing the existing turn.
Audit,
}

impl std::fmt::Display for InformationFlowMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Off => f.write_str("off"),
Self::Audit => f.write_str("audit"),
}
}
}

impl PermissionMode {
/// Return the wire-format string sent to the agent via
/// `session/set_config_option`.
Expand Down Expand Up @@ -443,6 +466,16 @@ pub struct CliArgs {
)]
pub permission_mode: PermissionMode,

/// Evaluate the audience-scoped information-flow design without enforcing it.
/// `off` is the default and leaves the existing harness behavior unchanged.
#[arg(
long,
env = "BUZZ_ACP_INFORMATION_FLOW",
default_value = "off",
value_enum
)]
pub information_flow: InformationFlowMode,

/// Inbound author gate: which authors' events the harness forwards.
/// Modes: owner-only (default), allowlist, anyone, nobody.
#[arg(
Expand Down Expand Up @@ -545,6 +578,8 @@ pub struct Config {
pub session_title: Option<String>,
/// Permission mode to apply after session creation. `Default` = skip.
pub permission_mode: PermissionMode,
/// Experimental IFC evaluator. `Off` is a true fast path with no policy queries.
pub information_flow: InformationFlowMode,
/// Inbound author gate mode.
pub respond_to: RespondTo,
/// Validated allowlist of pubkey hex strings (used when respond_to == Allowlist).
Expand Down Expand Up @@ -1110,6 +1145,7 @@ impl Config {
.as_deref()
.and_then(sanitize_session_title),
permission_mode: args.permission_mode,
information_flow: args.information_flow,
respond_to: args.respond_to,
respond_to_allowlist,
allowed_respond_to,
Expand Down Expand Up @@ -1143,7 +1179,7 @@ impl Config {
format!(" allowed_respond_to=[{}]", modes.join(","))
};
format!(
"relay={} pubkey={} agent_cmd={} {} mcp_cmd={} idle_timeout={}s max_turn={}s agents={} heartbeat={}s subscribe={:?} dedup={:?} meh={:?} ignore_self={} context_limit={} max_turns_per_session={} presence={} typing={} memory={} model={} permission_mode={} {}{}",
"relay={} pubkey={} agent_cmd={} {} mcp_cmd={} idle_timeout={}s max_turn={}s agents={} heartbeat={}s subscribe={:?} dedup={:?} meh={:?} ignore_self={} context_limit={} max_turns_per_session={} presence={} typing={} memory={} model={} permission_mode={} information_flow={} {}{}",
self.relay_url,
self.keys.public_key().to_hex(),
self.agent_command,
Expand All @@ -1164,6 +1200,7 @@ impl Config {
self.memory_enabled,
self.model.as_deref().unwrap_or("(agent default)"),
self.permission_mode,
self.information_flow,
respond_to_detail,
allowed_respond_to_detail,
)
Expand Down Expand Up @@ -1482,6 +1519,7 @@ mod tests {
model: None,
session_title: None,
permission_mode: PermissionMode::BypassPermissions,
information_flow: InformationFlowMode::Off,
respond_to: RespondTo::Anyone,
respond_to_allowlist: HashSet::new(),
allowed_respond_to: Vec::new(),
Expand Down Expand Up @@ -2205,6 +2243,22 @@ channels = "ALL"
assert_eq!(configured.exit_after_inactivity, 120);
}

#[test]
fn information_flow_is_default_off_and_audit_is_explicit() {
let key = "0".repeat(64);
let default = CliArgs::parse_from(["buzz-acp", "--private-key", &key]);
assert_eq!(default.information_flow, InformationFlowMode::Off);

let audit = CliArgs::parse_from([
"buzz-acp",
"--private-key",
&key,
"--information-flow",
"audit",
]);
assert_eq!(audit.information_flow, InformationFlowMode::Audit);
}

#[test]
fn lazy_pool_defaults_off() {
let key = "0".repeat(64);
Expand Down
Loading
Loading