Skip to content
Merged
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
53 changes: 52 additions & 1 deletion crates/sprout-acp/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,17 @@ pub struct CliArgs {
#[arg(long, env = "SPROUT_ACP_NO_TYPING")]
pub no_typing: bool,

/// Disable NIP-AE agent core memory injection.
///
/// When set, the harness skips the per-session core engram fetch and
/// renders prompts with no `[Agent Memory — core]` section, regardless of
/// whether a core engram exists on the relay. The `sprout mem` CLI and
/// the relay's acceptance of kind:30174 engrams are unaffected — this
/// flag is purely an opt-out for prompt-time injection in the ACP
/// harness.
#[arg(long, env = "SPROUT_ACP_NO_MEMORY")]
pub no_memory: bool,

/// Desired LLM model ID. Applied to every new ACP session after creation.
/// Use `sprout-acp models` to discover available model IDs.
#[arg(long, env = "SPROUT_ACP_MODEL")]
Expand Down Expand Up @@ -416,6 +427,11 @@ pub struct Config {
pub max_turns_per_session: u32,
pub presence_enabled: bool,
pub typing_enabled: bool,
/// Whether NIP-AE agent core memory injection is enabled. When false,
/// the harness skips the per-session core engram fetch and renders no
/// `[Agent Memory — core]` section. Mirrors the `--no-memory` /
/// `SPROUT_ACP_NO_MEMORY` opt-out.
pub memory_enabled: bool,
/// Desired LLM model ID. Applied after every `session_new_full()`.
pub model: Option<String>,
/// Permission mode to apply after session creation. `Default` = skip.
Expand Down Expand Up @@ -761,6 +777,7 @@ impl Config {
max_turns_per_session: args.max_turns_per_session,
presence_enabled: !args.no_presence,
typing_enabled: !args.no_typing,
memory_enabled: !args.no_memory,
model,
permission_mode: args.permission_mode,
respond_to: args.respond_to,
Expand All @@ -782,7 +799,7 @@ impl Config {
other => format!("respond_to={other}"),
};
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={} 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={} {}",
self.relay_url,
self.keys.public_key().to_hex(),
self.agent_command,
Expand All @@ -800,6 +817,7 @@ impl Config {
self.max_turns_per_session,
self.presence_enabled,
self.typing_enabled,
self.memory_enabled,
self.model.as_deref().unwrap_or("(agent default)"),
self.permission_mode,
respond_to_detail,
Expand Down Expand Up @@ -1122,6 +1140,7 @@ mod tests {
max_turns_per_session: 0,
presence_enabled: true,
typing_enabled: true,
memory_enabled: true,
model: None,
permission_mode: PermissionMode::BypassPermissions,
respond_to: RespondTo::Anyone,
Expand Down Expand Up @@ -1705,6 +1724,38 @@ channels = "ALL"
);
}

// ── no-memory toggle ────────────────────────────────────────────────────

#[test]
fn test_memory_enabled_default_true() {
let config = test_config(SubscribeMode::Mentions);
assert!(
config.memory_enabled,
"memory_enabled should default to true"
);
}

#[test]
fn test_summary_includes_memory_enabled() {
let config = test_config(SubscribeMode::Mentions);
let s = config.summary();
assert!(
s.contains("memory=true"),
"summary should include memory=true by default, got: {s}"
);
}

#[test]
fn test_summary_reflects_memory_disabled() {
let mut config = test_config(SubscribeMode::Mentions);
config.memory_enabled = false;
let s = config.summary();
assert!(
s.contains("memory=false"),
"summary should include memory=false when disabled, got: {s}"
);
}

// ── permission mode ─────────────────────────────────────────────────────

#[test]
Expand Down
9 changes: 9 additions & 0 deletions crates/sprout-acp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1089,8 +1089,16 @@ async fn tokio_main() -> Result<()> {
agent_owner_pubkey: startup_owner
.as_deref()
.and_then(|hex| nostr::PublicKey::from_hex(hex).ok()),
memory_enabled: config.memory_enabled,
});

if !config.memory_enabled {
tracing::info!(
target: "engram::core",
"NIP-AE core memory injection disabled (--no-memory / SPROUT_ACP_NO_MEMORY)"
);
}

// ── Step 6: Heartbeat timer ───────────────────────────────────────────────
let mut heartbeat = if config.heartbeat_interval_secs > 0 {
let interval = Duration::from_secs(config.heartbeat_interval_secs);
Expand Down Expand Up @@ -2753,6 +2761,7 @@ mod build_mcp_servers_tests {
max_turns_per_session: 0,
presence_enabled: true,
typing_enabled: true,
memory_enabled: true,
model: None,
permission_mode: config::PermissionMode::BypassPermissions,
respond_to: config::RespondTo::Anyone,
Expand Down
14 changes: 13 additions & 1 deletion crates/sprout-acp/src/pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,12 @@ pub struct PromptContext {
/// Owner pubkey (hex), if resolved at startup. When unset, NIP-AE core
/// injection is skipped entirely (no owner = no `(agent, owner)` pair).
pub agent_owner_pubkey: Option<nostr::PublicKey>,
/// Whether NIP-AE agent core memory injection is enabled. When false,
/// the per-session core engram fetch is skipped and `core_sections`
/// remains empty for every channel, so `format_prompt` renders no
/// `[Agent Memory — core]` section. Driven by `--no-memory` /
/// `SPROUT_ACP_NO_MEMORY`.
pub memory_enabled: bool,
}

// ── AgentPool impl ────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -767,7 +773,13 @@ pub async fn run_prompt_task(
// Per Tyler's locked spec: NO mid-session refreshes. Re-fetch only
// happens when a session is invalidated and recreated (see
// `SessionState::invalidate_channel`).
if is_new_session {
//
// Operator opt-out: `--no-memory` / `SPROUT_ACP_NO_MEMORY` disables the
// entire NIP-AE injection path. We skip the fetch outright and leave
// `state.core_sections` empty, so `format_prompt` renders no core
// section. The `sprout mem` CLI and the relay's acceptance of
// kind:30174 engrams are unaffected.
if is_new_session && ctx.memory_enabled {
if let (PromptSource::Channel(cid), Some(owner_pk)) =
(&source, ctx.agent_owner_pubkey.as_ref())
{
Expand Down
Loading