vv-agent 0.3.0

VectorVein agent runtime, SDK, CLI, tools, and workspace backends
Documentation
use super::*;

#[test]
fn runtime_microcompacts_before_full_memory_compaction() {
    let workspace = tempfile::tempdir().expect("workspace");
    let large_tool_payload = "tool output ".repeat(300);
    let llm = MicrocompactInspectingLlmClient::new(large_tool_payload);
    let inspector = llm.clone();
    let mut runtime = AgentRuntime::new(llm);
    runtime.default_workspace = Some(workspace.path().to_path_buf());
    runtime.workspace_backend = Arc::new(vv_agent::workspace::LocalWorkspaceBackend::new(
        workspace.path(),
    ));
    let mut task = AgentTask::new("microcompact_task", "demo", "system", "inspect memory");
    task.memory_compact_threshold = 10_000;
    task.metadata
        .insert("model_context_window".to_string(), json!(20_000));
    task.metadata
        .insert("reserved_output_tokens".to_string(), json!(0));
    task.metadata
        .insert("autocompact_buffer_tokens".to_string(), json!(0));
    task.metadata
        .insert("microcompact_trigger_ratio".to_string(), json!(0.01));
    task.metadata
        .insert("microcompact_keep_recent_cycles".to_string(), json!(0));
    task.metadata
        .insert("microcompact_min_result_length".to_string(), json!(200));

    let result = runtime.run(task).expect("run");

    assert_eq!(result.status, AgentStatus::Completed);
    let second_request = inspector.third_request_messages();
    assert!(
        second_request
            .iter()
            .any(|message| message.content == CLEARED_MARKER),
        "second request did not include microcompacted tool content: {second_request:#?}"
    );
    assert!(
        second_request
            .iter()
            .all(|message| !message.content.contains("<Compressed Agent Memory>")),
        "microcompact should avoid full summary before threshold: {second_request:#?}"
    );
}

#[test]
fn runtime_respects_configured_microcompact_tool_allowlist() {
    let workspace = tempfile::tempdir().expect("workspace");
    let large_tool_payload = "tool output ".repeat(300);
    let llm = MicrocompactInspectingLlmClient::new(large_tool_payload);
    let inspector = llm.clone();
    let mut runtime = AgentRuntime::new(llm);
    runtime.default_workspace = Some(workspace.path().to_path_buf());
    runtime.workspace_backend = Arc::new(vv_agent::workspace::LocalWorkspaceBackend::new(
        workspace.path(),
    ));
    let mut task = AgentTask::new(
        "microcompact_allowlist_task",
        "demo",
        "system",
        "inspect memory",
    );
    task.memory_compact_threshold = 10_000;
    task.metadata
        .insert("model_context_window".to_string(), json!(20_000));
    task.metadata
        .insert("reserved_output_tokens".to_string(), json!(0));
    task.metadata
        .insert("autocompact_buffer_tokens".to_string(), json!(0));
    task.metadata
        .insert("microcompact_trigger_ratio".to_string(), json!(0.01));
    task.metadata
        .insert("microcompact_keep_recent_cycles".to_string(), json!(0));
    task.metadata
        .insert("microcompact_min_result_length".to_string(), json!(200));
    task.metadata.insert(
        "microcompact_compactable_tools".to_string(),
        json!(["read_file"]),
    );

    let result = runtime.run(task).expect("run");

    assert_eq!(result.status, AgentStatus::Completed);
    let third_request = inspector.third_request_messages();
    assert!(
        third_request
            .iter()
            .all(|message| message.content != CLEARED_MARKER),
        "bash output should not be microcompacted when only read_file is allowlisted: {third_request:#?}"
    );
    assert!(
        third_request
            .iter()
            .any(|message| message.content.contains("tool output")),
        "original bash output should remain available: {third_request:#?}"
    );
}

#[test]
fn runtime_parses_string_float_microcompact_ratio() {
    let workspace = tempfile::tempdir().expect("workspace");
    let large_tool_payload = "tool output ".repeat(300);
    let llm = MicrocompactInspectingLlmClient::new(large_tool_payload);
    let inspector = llm.clone();
    let mut runtime = AgentRuntime::new(llm);
    runtime.default_workspace = Some(workspace.path().to_path_buf());
    runtime.workspace_backend = Arc::new(vv_agent::workspace::LocalWorkspaceBackend::new(
        workspace.path(),
    ));
    let mut task = AgentTask::new(
        "microcompact_string_ratio_task",
        "demo",
        "system",
        "inspect memory",
    );
    task.memory_compact_threshold = 10_000;
    task.metadata
        .insert("model_context_window".to_string(), json!(20_000));
    task.metadata
        .insert("reserved_output_tokens".to_string(), json!(0));
    task.metadata
        .insert("autocompact_buffer_tokens".to_string(), json!(0));
    task.metadata
        .insert("microcompact_trigger_ratio".to_string(), json!("0.01"));
    task.metadata
        .insert("microcompact_keep_recent_cycles".to_string(), json!(0));
    task.metadata
        .insert("microcompact_min_result_length".to_string(), json!(200));

    let result = runtime.run(task).expect("run");

    assert_eq!(result.status, AgentStatus::Completed);
    let third_request = inspector.third_request_messages();
    assert!(
        third_request
            .iter()
            .any(|message| message.content == CLEARED_MARKER),
        "string float microcompact ratio should trigger the same compaction: {third_request:#?}"
    );
}
#[derive(Clone)]
struct MicrocompactInspectingLlmClient {
    responses_seen: Arc<Mutex<usize>>,
    large_tool_payload: String,
    third_request_messages: Arc<Mutex<Vec<Message>>>,
}

impl MicrocompactInspectingLlmClient {
    fn new(large_tool_payload: String) -> Self {
        Self {
            responses_seen: Arc::new(Mutex::new(0)),
            large_tool_payload,
            third_request_messages: Arc::new(Mutex::new(Vec::new())),
        }
    }

    fn third_request_messages(&self) -> Vec<Message> {
        self.third_request_messages
            .lock()
            .expect("messages poisoned")
            .clone()
    }
}

impl LlmClient for MicrocompactInspectingLlmClient {
    fn complete(&self, request: LlmRequest) -> Result<LLMResponse, LlmError> {
        let mut responses_seen = self
            .responses_seen
            .lock()
            .map_err(|_| LlmError::Request("counter poisoned".to_string()))?;
        *responses_seen += 1;
        if *responses_seen == 1 {
            return Ok(LLMResponse::with_tool_calls(
                "first cycle",
                vec![ToolCall::new(
                    "bash_large",
                    "bash",
                    BTreeMap::from([(
                        "command".to_string(),
                        json!(format!("printf '{}'", self.large_tool_payload)),
                    )]),
                )],
            ));
        }
        if *responses_seen == 2 {
            return Ok(LLMResponse::new(
                "continue once so older tool output can age",
            ));
        }
        *self
            .third_request_messages
            .lock()
            .expect("messages poisoned") = request.messages.clone();
        Ok(LLMResponse::with_tool_calls(
            "finish",
            vec![ToolCall::new(
                "finish_after_microcompact",
                "task_finish",
                BTreeMap::from([("message".to_string(), json!("memory compacted"))]),
            )],
        ))
    }
}