Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e349a35716 | ||
|
|
f1f58b9996 |
@@ -1,3 +1,10 @@
|
|||||||
|
## [1.20.2](https://github.com/asepharyana/zesdex/compare/v1.20.1...v1.20.2) (2026-08-28)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **agent:** recall search + memory_dir fallback + bersihkan dead llm_client ([f1f58b9](https://github.com/asepharyana/zesdex/commit/f1f58b9996f8eb58871d44fdd41c2d863874f253))
|
||||||
|
|
||||||
## [1.20.1](https://github.com/asepharyana/zesdex/compare/v1.20.0...v1.20.1) (2026-08-28)
|
## [1.20.1](https://github.com/asepharyana/zesdex/compare/v1.20.0...v1.20.1) (2026-08-28)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Generated
+11
-11
@@ -4862,7 +4862,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zesdex-api"
|
name = "zesdex-api"
|
||||||
version = "1.19.6"
|
version = "1.20.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argon2",
|
"argon2",
|
||||||
@@ -4885,7 +4885,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zesdex-application"
|
name = "zesdex-application"
|
||||||
version = "1.19.6"
|
version = "1.20.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
@@ -4903,7 +4903,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zesdex-bootstrap"
|
name = "zesdex-bootstrap"
|
||||||
version = "1.19.6"
|
version = "1.20.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -4920,7 +4920,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zesdex-daemon"
|
name = "zesdex-daemon"
|
||||||
version = "1.19.6"
|
version = "1.20.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
@@ -4944,7 +4944,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zesdex-domain"
|
name = "zesdex-domain"
|
||||||
version = "1.19.6"
|
version = "1.20.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
@@ -4960,7 +4960,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zesdex-gateway"
|
name = "zesdex-gateway"
|
||||||
version = "1.19.6"
|
version = "1.20.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
@@ -4987,7 +4987,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zesdex-grpc"
|
name = "zesdex-grpc"
|
||||||
version = "1.19.6"
|
version = "1.20.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
@@ -5004,7 +5004,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zesdex-infrastructure"
|
name = "zesdex-infrastructure"
|
||||||
version = "1.19.6"
|
version = "1.20.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argon2",
|
"argon2",
|
||||||
@@ -5052,7 +5052,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zesdex-tui"
|
name = "zesdex-tui"
|
||||||
version = "1.19.6"
|
version = "1.20.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
@@ -5078,7 +5078,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zesdex-web"
|
name = "zesdex-web"
|
||||||
version = "1.19.6"
|
version = "1.20.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
@@ -5098,7 +5098,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zesdex-ws"
|
name = "zesdex-ws"
|
||||||
version = "1.19.6"
|
version = "1.20.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "1.20.1"
|
version = "1.20.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["asepharyana <superaseph@gmail.com>"]
|
authors = ["asepharyana <superaseph@gmail.com>"]
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,14 @@ impl ToolCtxBuilder {
|
|||||||
self.session_dir = v;
|
self.session_dir = v;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
pub fn memory_dir(mut self, v: PathBuf) -> Self {
|
||||||
|
self.memory_dir = v;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn worktrees_dir(mut self, v: PathBuf) -> Self {
|
||||||
|
self.worktrees_dir = v;
|
||||||
|
self
|
||||||
|
}
|
||||||
pub fn workspaces(mut self, v: Vec<PathBuf>) -> Self {
|
pub fn workspaces(mut self, v: Vec<PathBuf>) -> Self {
|
||||||
self.workspaces = v;
|
self.workspaces = v;
|
||||||
self
|
self
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ impl Tool for Forget {
|
|||||||
let name = crate::tools::arg_str(args, "name")?;
|
let name = crate::tools::arg_str(args, "name")?;
|
||||||
info!(name, "forget invoked");
|
info!(name, "forget invoked");
|
||||||
let repo = crate::persistence::cms::memory_repo::MarkdownMemoryRepository::new();
|
let repo = crate::persistence::cms::memory_repo::MarkdownMemoryRepository::new();
|
||||||
repo.delete(&ctx.memory_dir, &name)?;
|
let memory_dir = crate::tools::memory::resolve_memory_dir(&ctx.memory_dir);
|
||||||
|
repo.delete(&memory_dir, &name)?;
|
||||||
info!(name, "memory deleted");
|
info!(name, "memory deleted");
|
||||||
Ok(format!("Memory '{}' deleted", name))
|
Ok(format!("Memory '{}' deleted", name))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,21 @@
|
|||||||
//! Memory management tools — remember, recall, forget.
|
//! Memory management tools — remember, recall, forget.
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub mod forget;
|
pub mod forget;
|
||||||
pub mod recall;
|
pub mod recall;
|
||||||
pub mod remember;
|
pub mod remember;
|
||||||
|
|
||||||
|
/// Resolve the directory the memory tools should read/write.
|
||||||
|
///
|
||||||
|
/// Prefer an explicitly-configured `ToolCtx.memory_dir`. If that is empty
|
||||||
|
/// (a `ToolCtx` is often built without setting `memory_dir`), fall back to
|
||||||
|
/// the canonical persistent memory location from `Store` so memories are not
|
||||||
|
/// silently written into the current working directory.
|
||||||
|
pub fn resolve_memory_dir(ctx_memory_dir: &std::path::Path) -> PathBuf {
|
||||||
|
if ctx_memory_dir.as_os_str().is_empty() {
|
||||||
|
zesdex_domain::core::Store::new().memory_dir
|
||||||
|
} else {
|
||||||
|
ctx_memory_dir.to_path_buf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -45,21 +45,132 @@ impl Tool for Recall {
|
|||||||
#[instrument(skip(self, ctx, args))]
|
#[instrument(skip(self, ctx, args))]
|
||||||
fn run(&self, ctx: &ToolCtx, args: &Value) -> Result<String> {
|
fn run(&self, ctx: &ToolCtx, args: &Value) -> Result<String> {
|
||||||
let repo = crate::persistence::cms::memory_repo::MarkdownMemoryRepository::new();
|
let repo = crate::persistence::cms::memory_repo::MarkdownMemoryRepository::new();
|
||||||
|
let memory_dir = crate::tools::memory::resolve_memory_dir(&ctx.memory_dir);
|
||||||
|
|
||||||
let specific_name = args.get("name").and_then(|v| v.as_str());
|
let specific_name = args.get("name").and_then(|v| v.as_str());
|
||||||
|
let search = args.get("search").and_then(|v| v.as_str());
|
||||||
|
|
||||||
if let Some(name) = specific_name {
|
if let Some(name) = specific_name {
|
||||||
info!(name, "recall loading specific memory");
|
info!(name, "recall loading specific memory");
|
||||||
let memory = repo.load(&ctx.memory_dir, name)?;
|
let memory = repo.load(&memory_dir, name)?;
|
||||||
Ok(serde_json::to_string_pretty(&memory)?)
|
return Ok(serde_json::to_string_pretty(&memory)?);
|
||||||
} else {
|
|
||||||
info!("recall listing all memories");
|
|
||||||
let names = repo.list(&ctx.memory_dir)?;
|
|
||||||
if names.is_empty() {
|
|
||||||
info!("no memories found");
|
|
||||||
return Ok("No memories saved yet".to_string());
|
|
||||||
}
|
|
||||||
Ok(format!("Available memories:\n{}", names.join("\n")))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(query) = search {
|
||||||
|
let query = query.trim().to_lowercase();
|
||||||
|
info!(search = %query, "recall searching memories");
|
||||||
|
if query.is_empty() {
|
||||||
|
return Ok("Search query is empty".to_string());
|
||||||
|
}
|
||||||
|
let names = repo.list(&memory_dir)?;
|
||||||
|
let mut matches: Vec<String> = Vec::new();
|
||||||
|
for name in &names {
|
||||||
|
// Load each memory and match against name/description/content.
|
||||||
|
if let Ok(m) = repo.load(&memory_dir, name) {
|
||||||
|
let haystack =
|
||||||
|
format!("{} {} {}", m.name, m.description, m.content).to_lowercase();
|
||||||
|
if haystack.contains(&query) {
|
||||||
|
matches.push(m.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if matches.is_empty() {
|
||||||
|
return Ok(format!("No memories match '{query}'"));
|
||||||
|
}
|
||||||
|
return Ok(format!(
|
||||||
|
"Memories matching '{query}' ({}):\n{}",
|
||||||
|
matches.len(),
|
||||||
|
matches.join("\n")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("recall listing all memories");
|
||||||
|
let names = repo.list(&memory_dir)?;
|
||||||
|
if names.is_empty() {
|
||||||
|
info!("no memories found");
|
||||||
|
return Ok("No memories saved yet".to_string());
|
||||||
|
}
|
||||||
|
Ok(format!("Available memories:\n{}", names.join("\n")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::tools::ToolCtxBuilder;
|
||||||
|
use zesdex_domain::cms::MemoryRepository;
|
||||||
|
|
||||||
|
fn save_mem(name: &str, description: &str, content: &str, dir: &std::path::Path) {
|
||||||
|
let repo = crate::persistence::cms::memory_repo::MarkdownMemoryRepository::new();
|
||||||
|
let memory = zesdex_domain::cms::Memory {
|
||||||
|
name: name.to_string(),
|
||||||
|
description: description.to_string(),
|
||||||
|
content: content.to_string(),
|
||||||
|
kind: "reference".to_string(),
|
||||||
|
created_at: 0,
|
||||||
|
updated_at: 0,
|
||||||
|
outcome: None,
|
||||||
|
lifecycle: "active".to_string(),
|
||||||
|
scope: None,
|
||||||
|
before_snippet: None,
|
||||||
|
after_snippet: None,
|
||||||
|
provenances: Vec::new(),
|
||||||
|
};
|
||||||
|
repo.save(dir, &memory).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn search_filters_memories_by_keyword() {
|
||||||
|
let dir = std::env::temp_dir().join(format!("zdx-mem-test-{}", uuid::Uuid::new_v4()));
|
||||||
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
|
save_mem(
|
||||||
|
"rust-concurrency",
|
||||||
|
"tokio spawn patterns",
|
||||||
|
"how to use async tasks",
|
||||||
|
&dir,
|
||||||
|
);
|
||||||
|
save_mem(
|
||||||
|
"docker-deploy",
|
||||||
|
"deploy via compose",
|
||||||
|
"container orchestration",
|
||||||
|
&dir,
|
||||||
|
);
|
||||||
|
|
||||||
|
let tool_ctx = ToolCtxBuilder::default().memory_dir(dir.clone()).build();
|
||||||
|
let args = serde_json::json!({ "search": "tokio" });
|
||||||
|
|
||||||
|
let out = Recall.run(&tool_ctx, &args).unwrap();
|
||||||
|
assert!(
|
||||||
|
out.contains("rust-concurrency"),
|
||||||
|
"should match rust-concurrency, got: {out}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!out.contains("docker-deploy"),
|
||||||
|
"docker-deploy should not match tokio"
|
||||||
|
);
|
||||||
|
|
||||||
|
// A query with no match reports so.
|
||||||
|
let no_match = Recall
|
||||||
|
.run(&tool_ctx, &serde_json::json!({ "search": "zzzznope" }))
|
||||||
|
.unwrap();
|
||||||
|
assert!(no_match.contains("No memories match"), "{no_match}");
|
||||||
|
|
||||||
|
std::fs::remove_dir_all(&dir).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolve_memory_dir_falls_back_to_store_when_empty() {
|
||||||
|
// An empty ToolCtx.memory_dir is resolved to the canonical Store path.
|
||||||
|
let resolved = crate::tools::memory::resolve_memory_dir(std::path::Path::new(""));
|
||||||
|
assert!(!resolved.as_os_str().is_empty());
|
||||||
|
assert!(
|
||||||
|
resolved.ends_with("memory"),
|
||||||
|
"expected memory dir, got {resolved:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
// An explicit memory_dir is preserved.
|
||||||
|
let explicit =
|
||||||
|
crate::tools::memory::resolve_memory_dir(std::path::Path::new("/tmp/custom-memory"));
|
||||||
|
assert_eq!(explicit, std::path::Path::new("/tmp/custom-memory"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,8 @@ impl Tool for Remember {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let repo = crate::persistence::cms::memory_repo::MarkdownMemoryRepository::new();
|
let repo = crate::persistence::cms::memory_repo::MarkdownMemoryRepository::new();
|
||||||
repo.save(&ctx.memory_dir, &memory)?;
|
let memory_dir = crate::tools::memory::resolve_memory_dir(&ctx.memory_dir);
|
||||||
|
repo.save(&memory_dir, &memory)?;
|
||||||
|
|
||||||
info!(name, "memory saved");
|
info!(name, "memory saved");
|
||||||
Ok(format!("Memory '{}' saved", name))
|
Ok(format!("Memory '{}' saved", name))
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ use anyhow::Result;
|
|||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
use tracing::{debug, info, instrument, warn};
|
use tracing::{debug, info, instrument, warn};
|
||||||
|
|
||||||
use crate::llm::provider::LlmClient;
|
|
||||||
use crate::tools::{arg_str, Tool, ToolCtx};
|
use crate::tools::{arg_str, Tool, ToolCtx};
|
||||||
use crate::workflow::engine::execution::execute_workflow;
|
use crate::workflow::engine::execution::execute_workflow;
|
||||||
use crate::workflow::hive_mind::cycle::execute_cycle;
|
use crate::workflow::hive_mind::cycle::execute_cycle;
|
||||||
@@ -60,14 +59,8 @@ impl Tool for WorkflowRun {
|
|||||||
phase_names.join(", ")
|
phase_names.join(", ")
|
||||||
);
|
);
|
||||||
|
|
||||||
let llm_client = LlmClient::new(
|
|
||||||
crate::llm::provider::DEFAULT_API_KEY.to_string(),
|
|
||||||
zesdex_domain::agent::defaults::DEFAULT_MODEL.to_string(),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
let rt = crate::runtime::runtime();
|
let rt = crate::runtime::runtime();
|
||||||
let result: Vec<String> =
|
let result: Vec<String> = rt.block_on(async { execute_workflow(&script, ctx).await })?;
|
||||||
rt.block_on(async { execute_workflow(&script, ctx, &llm_client).await })?;
|
|
||||||
|
|
||||||
info!(phase_count = result.len(), "Workflow completed");
|
info!(phase_count = result.len(), "Workflow completed");
|
||||||
Ok(format!(
|
Ok(format!(
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tracing::{info, instrument};
|
use tracing::{info, instrument};
|
||||||
|
|
||||||
use crate::llm::provider::LlmClient;
|
|
||||||
use crate::tools::ToolCtx;
|
use crate::tools::ToolCtx;
|
||||||
use crate::workflow::engine::primitives::execute_primitive;
|
use crate::workflow::engine::primitives::execute_primitive;
|
||||||
use zesdex_domain::workflow::WorkflowScript;
|
use zesdex_domain::workflow::WorkflowScript;
|
||||||
@@ -11,12 +10,8 @@ use zesdex_domain::workflow::WorkflowScript;
|
|||||||
/// Execute each phase of a workflow script sequentially.
|
/// Execute each phase of a workflow script sequentially.
|
||||||
///
|
///
|
||||||
/// Flow: for each phase → execute_primitive → collect result.
|
/// Flow: for each phase → execute_primitive → collect result.
|
||||||
#[instrument(skip(tool_ctx, _llm_client))]
|
#[instrument(skip(tool_ctx))]
|
||||||
pub async fn execute_workflow(
|
pub async fn execute_workflow(script: &WorkflowScript, tool_ctx: &ToolCtx) -> Result<Vec<String>> {
|
||||||
script: &WorkflowScript,
|
|
||||||
tool_ctx: &ToolCtx,
|
|
||||||
_llm_client: &LlmClient,
|
|
||||||
) -> Result<Vec<String>> {
|
|
||||||
info!(
|
info!(
|
||||||
"Executing workflow: {} ({} phases)",
|
"Executing workflow: {} ({} phases)",
|
||||||
script.name,
|
script.name,
|
||||||
|
|||||||
Reference in New Issue
Block a user