From 3082b86baa671e625c09c58f3e4c707d348afbe8 Mon Sep 17 00:00:00 2001 From: Geoff Johnson Date: Mon, 23 Mar 2026 09:27:06 -0700 Subject: [PATCH 1/4] refactor(common): extract resolve_data_dir helper from get_db_path From d9f2e1d8c99dbf2ab23a8c9b91f08a6111e768cb Mon Sep 17 00:00:00 2001 From: Geoff Johnson Date: Mon, 23 Mar 2026 09:28:09 -0700 Subject: [PATCH 2/4] refactor(common): extract resolve_data_dir helper from get_db_path (closes #722) --- crates/common/src/storage.rs | 78 +++++++++++++++++++++++++++++++----- 1 file changed, 69 insertions(+), 9 deletions(-) diff --git a/crates/common/src/storage.rs b/crates/common/src/storage.rs index 0824f4ed..8cf92e8a 100644 --- a/crates/common/src/storage.rs +++ b/crates/common/src/storage.rs @@ -38,19 +38,34 @@ use std::path::{Path, PathBuf}; /// // macOS: ~/Library/Application Support/agentd-notify/notify.db /// // Linux: ~/.local/share/agentd-notify/notify.db /// ``` -pub fn get_db_path(project_name: &str, db_filename: &str) -> Result { - let data_dir = match std::env::var("AGENTD_ENV").as_deref() { - Ok("development" | "dev") => PathBuf::from("tmp"), - Ok("test") => PathBuf::from("tmp/test"), +/// Resolve the data directory for a project based on the `AGENTD_ENV` +/// environment variable. +/// +/// | `AGENTD_ENV` value | Resolved path | +/// |---|---| +/// | `development` or `dev` | `tmp/` | +/// | `test` | `tmp/test/` | +/// | *(absent or any other value)* | XDG data dir for `project_name` | +/// +/// Extracting this logic into its own function makes it independently +/// testable: callers can set `AGENTD_ENV` and verify the result without +/// triggering directory creation or database connection setup. +fn resolve_data_dir(project_name: &str) -> Result { + match std::env::var("AGENTD_ENV").as_deref() { + Ok("development" | "dev") => Ok(PathBuf::from("tmp")), + Ok("test") => Ok(PathBuf::from("tmp/test")), _ => { - let proj_dirs = ProjectDirs::from("", "", project_name) - .ok_or_else(|| anyhow::anyhow!("Failed to determine project directories"))?; - proj_dirs.data_dir().to_path_buf() + let proj_dirs = ProjectDirs::from("", "", project_name).ok_or_else(|| { + anyhow::anyhow!("Failed to determine project directories for '{}'", project_name) + })?; + Ok(proj_dirs.data_dir().to_path_buf()) } - }; + } +} +pub fn get_db_path(project_name: &str, db_filename: &str) -> Result { + let data_dir = resolve_data_dir(project_name)?; std::fs::create_dir_all(&data_dir)?; - Ok(data_dir.join(db_filename)) } @@ -124,6 +139,51 @@ pub async fn migration_status( mod tests { use super::*; + // ------------------------------------------------------------------------- + // resolve_data_dir tests + // ------------------------------------------------------------------------- + // All AGENTD_ENV variants are exercised in a single test to prevent + // data races between parallel test threads that share the process + // environment. + + #[test] + fn test_resolve_data_dir_development() { + std::env::set_var("AGENTD_ENV", "development"); + let dir = resolve_data_dir("agentd-test-common").unwrap(); + assert_eq!(dir, PathBuf::from("tmp")); + std::env::remove_var("AGENTD_ENV"); + } + + #[test] + fn test_resolve_data_dir_dev_shorthand() { + std::env::set_var("AGENTD_ENV", "dev"); + let dir = resolve_data_dir("agentd-test-common").unwrap(); + assert_eq!(dir, PathBuf::from("tmp")); + std::env::remove_var("AGENTD_ENV"); + } + + #[test] + fn test_resolve_data_dir_test() { + std::env::set_var("AGENTD_ENV", "test"); + let dir = resolve_data_dir("agentd-test-common").unwrap(); + assert_eq!(dir, PathBuf::from("tmp/test")); + std::env::remove_var("AGENTD_ENV"); + } + + #[test] + fn test_resolve_data_dir_production_contains_project_name() { + std::env::remove_var("AGENTD_ENV"); + let dir = resolve_data_dir("agentd-test-common").unwrap(); + assert!( + dir.to_string_lossy().contains("agentd-test-common"), + "production data dir should contain the project name" + ); + } + + // ------------------------------------------------------------------------- + // get_db_path tests (existing, kept for regression coverage) + // ------------------------------------------------------------------------- + #[test] fn test_get_db_path_respects_agentd_env() { // Run all AGENTD_ENV variants in a single test to avoid env var races From 3c022721fa3819bf2dad1b324380f7ab0fa0ce05 Mon Sep 17 00:00:00 2001 From: Geoff Johnson Date: Tue, 12 May 2026 16:03:11 -0700 Subject: [PATCH 3/4] fix: collapse racy resolve_data_dir tests into single sequential test, fix doc comments --- .agentd/agents/architect.yml | 56 + .agentd/agents/conductor.yml | 3 + .agentd/agents/designer.yml | 3 + .agentd/agents/documenter.yml | 3 + .agentd/agents/enricher.yml | 31 + .agentd/agents/planner.yml | 43 + .agentd/agents/refactor.yml | 28 + .agentd/agents/release-manager.yml | 3 + .agentd/agents/research.yml | 20 + .agentd/agents/reviewer.yml | 33 + .agentd/agents/security.yml | 32 + .agentd/agents/tester.yml | 5 + .agentd/agents/triage.yml | 21 +- .agentd/agents/worker.yml | 64 +- .agentd/rooms/announcements.yml | 2 + .agentd/rooms/engineering.yml | 2 + .agentd/rooms/operations.yml | 2 + .agentd/rooms/security.yml | 2 + .agentd/workflows/conductor-sync-manual.yml | 2 + .agentd/workflows/conductor-sync.yml | 2 + .agentd/workflows/design-worker.yml | 2 + .agentd/workflows/docs-worker.yml | 2 + .agentd/workflows/enrichment-worker.yml | 6 + .agentd/workflows/epic-issue-worker.yml | 243 +++++ .agentd/workflows/examples/README.md | 108 ++ .../examples/daily-nutrition-analyze.yml | 58 ++ .../examples/daily-nutrition-ask.yml | 46 + .../examples/deploy-approval-ask.yml | 56 + .../examples/deploy-approval-respond.yml | 72 ++ .../workflows/examples/productivity-check.yml | 44 + .../examples/productivity-followup.yml | 62 ++ .agentd/workflows/issue-worker.yml | 31 +- .agentd/workflows/merge-worker.yml | 2 + .agentd/workflows/plan-worker.yml | 5 + .agentd/workflows/pull-request-reviewer.yml | 8 +- .agentd/workflows/pull-request-rework.yml | 69 ++ .agentd/workflows/refactor-review.yml | 4 + .agentd/workflows/refactor-worker.yml | 6 + .agentd/workflows/research-worker.yml | 4 + .agentd/workflows/review-merge-chain.yml | 2 + .agentd/workflows/security-audit.yml | 6 + .agentd/workflows/security-worker.yml | 6 + .agentd/workflows/test-worker.yml | 2 + .agentd/workflows/triage-enrich-chain.yml | 2 + .agentd/workflows/triage-worker.yml | 4 + .cargo/audit.toml | 18 +- .cargo/config.toml | 9 + baml_src/README.md | 626 ----------- baml_src/cli.baml | 441 -------- baml_src/clients.baml | 171 --- baml_src/generators.baml | 12 - baml_src/hooks.baml | 528 ---------- baml_src/monitoring.baml | 533 ---------- baml_src/notifications.baml | 288 ----- baml_src/questions.baml | 415 -------- crates/baml/Cargo.toml | 29 - crates/baml/README.md | 488 --------- .../baml/examples/categorize_notification.rs | 121 --- crates/baml/src/client.rs | 980 ------------------ crates/baml/src/error.rs | 42 - crates/baml/src/lib.rs | 138 --- crates/baml/src/types.rs | 456 -------- crates/common/src/storage.rs | 74 +- ui/src/styles/themes.ts | 142 --- 64 files changed, 1247 insertions(+), 5471 deletions(-) create mode 100644 .agentd/workflows/epic-issue-worker.yml create mode 100644 .agentd/workflows/examples/README.md create mode 100644 .agentd/workflows/examples/daily-nutrition-analyze.yml create mode 100644 .agentd/workflows/examples/daily-nutrition-ask.yml create mode 100644 .agentd/workflows/examples/deploy-approval-ask.yml create mode 100644 .agentd/workflows/examples/deploy-approval-respond.yml create mode 100644 .agentd/workflows/examples/productivity-check.yml create mode 100644 .agentd/workflows/examples/productivity-followup.yml create mode 100644 .agentd/workflows/pull-request-rework.yml delete mode 100644 baml_src/README.md delete mode 100644 baml_src/cli.baml delete mode 100644 baml_src/clients.baml delete mode 100644 baml_src/generators.baml delete mode 100644 baml_src/hooks.baml delete mode 100644 baml_src/monitoring.baml delete mode 100644 baml_src/notifications.baml delete mode 100644 baml_src/questions.baml delete mode 100644 crates/baml/Cargo.toml delete mode 100644 crates/baml/README.md delete mode 100644 crates/baml/examples/categorize_notification.rs delete mode 100644 crates/baml/src/client.rs delete mode 100644 crates/baml/src/error.rs delete mode 100644 crates/baml/src/lib.rs delete mode 100644 crates/baml/src/types.rs delete mode 100644 ui/src/styles/themes.ts diff --git a/.agentd/agents/architect.yml b/.agentd/agents/architect.yml index 9412cf96..4e30b98d 100644 --- a/.agentd/agents/architect.yml +++ b/.agentd/agents/architect.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/agent.yml + # Architect agent — cross-service design review and ADR authoring. # # Reviews PRs labeled "architecture" for cross-crate design concerns, authors @@ -16,6 +18,31 @@ shell: /bin/zsh worktree: false model: claude-opus-4-6 +# Tool policy: codebase read access + tools for applying triage results +tool_policy: + mode: allow_list + tools: + - Read + - Grep + - Glob + - WebFetch + - "Bash(agent memory *)" + - "Bash(gh pr *)" + - "Bash(grep *)" + - "Bash(ls *)" + - "Bash(diff *)" + - "Bash(gh issue create *)" + - "Bash(gh issue edit *)" + - "Bash(gh issue view *)" + - "Bash(gh issue list *)" + - "Bash(gh issue comment *)" + - "Bash(gh label list *)" + sandbox_bypass: + - "Bash(agent *)" + - "Bash(git-spice branch submit *)" + - "Bash(git-spice repo sync*)" + - "Bash(gh pr create *)" + rooms: - engineering - name: announcements @@ -29,6 +56,7 @@ env: AGENTD_WRAP_SERVICE_URL: http://localhost:7005 AGENTD_ORCHESTRATOR_SERVICE_URL: http://localhost:7006 AGENTD_MEMORY_SERVICE_URL: http://localhost:7008 + AGENTD_INDEX_SERVICE_URL: http://localhost:7012 AGENTD_COMMUNICATE_SERVICE_URL: http://localhost:7010 system_prompt: | @@ -329,3 +357,31 @@ system_prompt: | - PR-specific feedback already posted as GitHub comments - ADR content already written to `docs/planning/decisions/` - Standard Rust patterns (use Rust docs for those) + + ## Code Index Protocol + + You have access to a semantic code search service via the `agent index` CLI. + Use it for cross-crate analysis and service boundary verification. The index + complements memory: memory stores human knowledge and decisions; index searches + actual source code. If the index service is unavailable, fall back to `grep -r`. + + ### During Architecture Reviews + 1. Find all implementations of a trait across crates: + agent index search "" --mode keyword --hierarchy symbol --limit 20 --json + 2. Check for cross-crate type usage (potential boundary violations): + agent index search "use agentd_" --mode keyword --limit 20 --json + 3. Find all API route definitions to check pattern compliance: + agent index search "Router::new route" --mode hybrid --file-pattern "crates/*/src/api*" --limit 20 --json + 4. Search for duplicate type definitions across crates: + agent index search "" --mode keyword --hierarchy symbol --limit 20 --json + + ### ADR Research + When assessing whether an ADR is needed, search for existing patterns: + agent index search "" --mode vector --language rust --limit 10 --json + + ### Search Mode Guide + - Use `--mode keyword` for precise cross-crate import analysis + - Use `--mode hybrid` for architectural pattern searches + - Use `--mode vector` for conceptual queries about design approaches + - Add `--hierarchy symbol` for trait/struct/enum discovery + - Add `--hierarchy directory` for module-level architectural overview diff --git a/.agentd/agents/conductor.yml b/.agentd/agents/conductor.yml index fff4370a..2f043eca 100644 --- a/.agentd/agents/conductor.yml +++ b/.agentd/agents/conductor.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/agent.yml + # Conductor agent — pipeline orchestration and merge queue management. # # The conductor owns the end-to-end issue lifecycle: dispatching work, @@ -50,6 +52,7 @@ env: AGENTD_WRAP_SERVICE_URL: http://localhost:7005 AGENTD_ORCHESTRATOR_SERVICE_URL: http://localhost:7006 AGENTD_MEMORY_SERVICE_URL: http://localhost:7008 + AGENTD_INDEX_SERVICE_URL: http://localhost:7012 AGENTD_COMMUNICATE_SERVICE_URL: http://localhost:7010 system_prompt: | diff --git a/.agentd/agents/designer.yml b/.agentd/agents/designer.yml index 6cb4e35e..2ea43910 100644 --- a/.agentd/agents/designer.yml +++ b/.agentd/agents/designer.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/agent.yml + # Designer agent — maintains UI design, design system, and visual consistency. # # Usage: @@ -38,6 +40,7 @@ env: AGENTD_WRAP_SERVICE_URL: http://localhost:7005 AGENTD_ORCHESTRATOR_SERVICE_URL: http://localhost:7006 AGENTD_MEMORY_SERVICE_URL: http://localhost:7008 + AGENTD_INDEX_SERVICE_URL: http://localhost:7012 AGENTD_COMMUNICATE_SERVICE_URL: http://localhost:7010 system_prompt: | diff --git a/.agentd/agents/documenter.yml b/.agentd/agents/documenter.yml index bd368260..9d806cca 100644 --- a/.agentd/agents/documenter.yml +++ b/.agentd/agents/documenter.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/agent.yml + # Documentation agent — maintains and improves project documentation. # # Usage: @@ -34,6 +36,7 @@ env: AGENTD_WRAP_SERVICE_URL: http://localhost:7005 AGENTD_ORCHESTRATOR_SERVICE_URL: http://localhost:7006 AGENTD_MEMORY_SERVICE_URL: http://localhost:7008 + AGENTD_INDEX_SERVICE_URL: http://localhost:7012 AGENTD_COMMUNICATE_SERVICE_URL: http://localhost:7010 system_prompt: | diff --git a/.agentd/agents/enricher.yml b/.agentd/agents/enricher.yml index 0eb910b5..cbaa63f8 100644 --- a/.agentd/agents/enricher.yml +++ b/.agentd/agents/enricher.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/agent.yml + # Enricher agent — improves issue quality before worker implementation. # # Triggered when an issue is labeled with "enrich-agent". Reads the issue, @@ -27,6 +29,7 @@ env: AGENTD_WRAP_SERVICE_URL: http://localhost:7005 AGENTD_ORCHESTRATOR_SERVICE_URL: http://localhost:7006 AGENTD_MEMORY_SERVICE_URL: http://localhost:7008 + AGENTD_INDEX_SERVICE_URL: http://localhost:7012 AGENTD_COMMUNICATE_SERVICE_URL: http://localhost:7010 system_prompt: | @@ -193,3 +196,31 @@ system_prompt: | ### What NOT to Remember - Issue-specific content already captured in GitHub comments - File paths and type names already in the source code + + ## Code Index Protocol + + You have access to a semantic code search service via the `agent index` CLI. + Use it instead of grep to find relevant code when enriching issues. The index + provides semantic understanding, not just text matching. The index complements + memory: memory stores human knowledge and decisions; index searches actual source + code. If the index service is unavailable, fall back to `grep -r`. + + ### During Enrichment + 1. Search for code related to the issue topic: + agent index search "" --mode vector --language rust --limit 10 --json + 2. Find specific types, functions, or modules mentioned in the issue: + agent index search "" --mode keyword --hierarchy symbol --limit 5 --json + 3. Find test patterns for the affected area: + agent index search " test" --mode hybrid --file-pattern "crates/*/src/**" --limit 5 --json + + ### Enrichment-Specific Uses + - When listing "Relevant Files": use index search results instead of guessing + - When writing "Implementation Hints": search for existing patterns the implementer should follow + - When suggesting "Test Cases": search for existing test patterns in the affected crate + - When assessing scope: search to understand how many files touch the affected area + + ### Search Mode Guide + - Use `--mode vector` for mapping issue descriptions to relevant code + - Use `--mode keyword` for specific identifier lookups + - Use `--mode hybrid` when the issue mentions both concepts and specific names + - Add `--hierarchy symbol` to find the exact functions/structs an implementer needs diff --git a/.agentd/agents/planner.yml b/.agentd/agents/planner.yml index 470c5402..47f8436e 100644 --- a/.agentd/agents/planner.yml +++ b/.agentd/agents/planner.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/agent.yml + # Planning agent — analyzes the agentd codebase and proposes GitHub issues. # # Usage: @@ -9,6 +11,27 @@ shell: /bin/zsh worktree: false model: claude-opus-4-6 +# Tool policy: codebase read access + tools for applying triage results +tool_policy: + mode: allow_list + tools: + - Read + - Grep + - Glob + - WebFetch + - "Bash(gh issue create *)" + - "Bash(gh issue edit *)" + - "Bash(gh issue view *)" + - "Bash(gh issue list *)" + - "Bash(gh issue comment *)" + - "Bash(gh label list *)" + - "Bash(agent memory *)" + sandbox_bypass: + - "Bash(agent *)" + - "Bash(git-spice branch submit *)" + - "Bash(git-spice repo sync*)" + - "Bash(gh pr create *)" + rooms: - engineering - name: announcements @@ -22,6 +45,7 @@ env: AGENTD_WRAP_SERVICE_URL: http://localhost:7005 AGENTD_ORCHESTRATOR_SERVICE_URL: http://localhost:7006 AGENTD_MEMORY_SERVICE_URL: http://localhost:7008 + AGENTD_INDEX_SERVICE_URL: http://localhost:7012 AGENTD_COMMUNICATE_SERVICE_URL: http://localhost:7010 system_prompt: | @@ -148,3 +172,22 @@ system_prompt: | ### What NOT to Remember - Issue contents already captured in GitHub - Information already in code, comments, or git history + + ## Code Index Protocol + + You have access to a semantic code search service via the `agent index` CLI. + Use it to understand codebase structure when planning work. If the index + service is unavailable, skip this step. + + ### During Planning + 1. Survey existing implementations before proposing new work: + agent index search "" --mode vector --language rust --limit 10 --json + 2. Verify assumptions about what exists: + agent index search "" --mode keyword --hierarchy symbol --limit 5 --json + 3. When estimating scope, search to understand the affected code surface: + agent index search "" --mode hybrid --limit 10 --json + + ### Planning-Specific Uses + - Before proposing an issue: search to verify the feature does not already exist + - When defining dependencies between issues: search to understand which crates share types + - Add `--hierarchy directory` for module-level overviews diff --git a/.agentd/agents/refactor.yml b/.agentd/agents/refactor.yml index 699bb0a2..17f6926e 100644 --- a/.agentd/agents/refactor.yml +++ b/.agentd/agents/refactor.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/agent.yml + # Refactor agent — identifies and implements code improvement opportunities. # # Operates in two modes: @@ -30,6 +32,11 @@ tool_policy: - "Edit(docs/**)" - "Write(CLAUDE.md)" - "Edit(CLAUDE.md)" + sandbox_bypass: + - "Bash(agent *)" + - "Bash(git-spice branch submit *)" + - "Bash(git-spice repo sync*)" + - "Bash(gh pr create *)" rooms: - engineering @@ -44,6 +51,7 @@ env: AGENTD_WRAP_SERVICE_URL: http://localhost:7005 AGENTD_ORCHESTRATOR_SERVICE_URL: http://localhost:7006 AGENTD_MEMORY_SERVICE_URL: http://localhost:7008 + AGENTD_INDEX_SERVICE_URL: http://localhost:7012 AGENTD_COMMUNICATE_SERVICE_URL: http://localhost:7010 system_prompt: | @@ -361,3 +369,23 @@ system_prompt: | ### What NOT to Remember - Issue contents already captured in GitHub - Standard Rust idioms (use clippy for those) + + ## Code Index Protocol + + You have access to a semantic code search service via the `agent index` CLI. + Use it to find all call sites and usages before refactoring. The index + complements memory: memory stores human knowledge and decisions; index searches + actual source code. If the index service is unavailable, fall back to `grep -r`. + + ### Before Each Refactor + 1. Find all usages of the type/function being refactored: + agent index search "" --mode keyword --hierarchy symbol --limit 20 --json + 2. Find all callers across crates: + agent index search "" --mode keyword --limit 20 --json + 3. Search for similar patterns that should be refactored consistently: + agent index search "" --mode hybrid --language rust --limit 10 --json + + ### Search Mode Guide + - Use `--mode keyword` for precise call-site discovery + - Use `--mode hybrid` for finding similar patterns across the codebase + - Add `--hierarchy symbol` for function/struct/trait lookups diff --git a/.agentd/agents/release-manager.yml b/.agentd/agents/release-manager.yml index f668108f..ab170175 100644 --- a/.agentd/agents/release-manager.yml +++ b/.agentd/agents/release-manager.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/agent.yml + # Release manager agent — changelog generation, version bumping, and GitHub releases. # # Manually triggered via the "release" label on a release issue. The issue @@ -30,6 +32,7 @@ env: AGENTD_WRAP_SERVICE_URL: http://localhost:7005 AGENTD_ORCHESTRATOR_SERVICE_URL: http://localhost:7006 AGENTD_MEMORY_SERVICE_URL: http://localhost:7008 + AGENTD_INDEX_SERVICE_URL: http://localhost:7012 AGENTD_COMMUNICATE_SERVICE_URL: http://localhost:7010 system_prompt: | diff --git a/.agentd/agents/research.yml b/.agentd/agents/research.yml index 0dc5b712..0c4bc374 100644 --- a/.agentd/agents/research.yml +++ b/.agentd/agents/research.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/agent.yml + # Research agent — technology investigation and architectural analysis. # # Investigates crate ecosystems, protocols, design patterns, and architectural @@ -36,6 +38,7 @@ env: AGENTD_WRAP_SERVICE_URL: http://localhost:7005 AGENTD_ORCHESTRATOR_SERVICE_URL: http://localhost:7006 AGENTD_MEMORY_SERVICE_URL: http://localhost:7008 + AGENTD_INDEX_SERVICE_URL: http://localhost:7012 AGENTD_COMMUNICATE_SERVICE_URL: http://localhost:7010 system_prompt: | @@ -322,3 +325,20 @@ system_prompt: | - Research findings already posted as issue comments (already in GitHub) - Information already captured in source code or documentation - Ephemeral findings that depend on a specific version and will likely change + + ## Code Index Protocol + + You have access to a semantic code search service via the `agent index` CLI. + Use it to investigate codebase patterns during research. If the index service + is unavailable, fall back to `grep -r`. + + ### During Research + 1. Search for existing implementations of the pattern being researched: + agent index search "" --mode vector --language rust --limit 10 --json + 2. Find all usages of a specific library or crate: + agent index search "" --mode keyword --limit 10 --json + + ### Search Mode Guide + - Use `--mode vector` for broad exploratory searches + - Use `--mode keyword` for precise identifier lookups + - Use `--mode hybrid` for mixed queries diff --git a/.agentd/agents/reviewer.yml b/.agentd/agents/reviewer.yml index 6c9bb61a..be65f30e 100644 --- a/.agentd/agents/reviewer.yml +++ b/.agentd/agents/reviewer.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/agent.yml + # PR reviewer agent — performs autonomous code reviews on pull requests. # # Usage: @@ -23,6 +25,7 @@ env: AGENTD_WRAP_SERVICE_URL: http://localhost:7005 AGENTD_ORCHESTRATOR_SERVICE_URL: http://localhost:7006 AGENTD_MEMORY_SERVICE_URL: http://localhost:7008 + AGENTD_INDEX_SERVICE_URL: http://localhost:7012 AGENTD_COMMUNICATE_SERVICE_URL: http://localhost:7010 system_prompt: | @@ -122,3 +125,33 @@ system_prompt: | ### What NOT to Remember - PR-specific feedback already captured in GitHub comments - Information already in code, comments, or git history + + ## Code Index Protocol + + You have access to a semantic code search service via the `agent index` CLI. + Use it to understand the broader context of code changes during reviews. The + index complements memory: memory stores human knowledge and decisions; index + searches actual source code. If the index service is unavailable, fall back + to `grep -r`. + + ### Before Each Review + After reading the PR diff, use the index to understand context: + 1. For each changed function or type, search for related code: + agent index search "" --mode keyword --hierarchy symbol --limit 5 --json + 2. Find callers and dependents of changed interfaces: + agent index search "" --mode hybrid --language rust --limit 10 --json + 3. Check if the PR's patterns are consistent with existing code: + agent index search "" --mode vector --limit 5 --json + + ### Review-Specific Uses + - When a PR adds a new function: search for similar existing functions to check naming consistency + - When a PR changes a public API: search for all callers to assess impact + - When a PR adds error handling: search for the project's error handling patterns + - When a PR touches shared types: search for all usages across crates + - Before approving API changes: verify all callers are covered by the PR or its tests + + ### Search Mode Guide + - Use `--mode keyword` for exact symbol lookups from the diff + - Use `--mode hybrid` for finding related code patterns + - Add `--hierarchy symbol` to find specific functions/structs/traits + - Add `--file-pattern "crates//src/**"` to scope to a specific crate diff --git a/.agentd/agents/security.yml b/.agentd/agents/security.yml index 3be45a14..4bc2064e 100644 --- a/.agentd/agents/security.yml +++ b/.agentd/agents/security.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/agent.yml + # Security agent — proactive security auditing and vulnerability management. # # Triggered by the "security-agent" label on an issue, or on a weekly cron @@ -44,6 +46,7 @@ env: AGENTD_WRAP_SERVICE_URL: http://localhost:7005 AGENTD_ORCHESTRATOR_SERVICE_URL: http://localhost:7006 AGENTD_MEMORY_SERVICE_URL: http://localhost:7008 + AGENTD_INDEX_SERVICE_URL: http://localhost:7012 AGENTD_COMMUNICATE_SERVICE_URL: http://localhost:7010 system_prompt: | @@ -314,3 +317,32 @@ system_prompt: | ### What NOT to Remember - Full advisory text (available in RUSTSEC database) - Issue numbers already tracked in GitHub + + ## Code Index Protocol + + You have access to a semantic code search service via the `agent index` CLI. + Use it to find security-relevant code patterns across the codebase. The index + complements memory: memory stores human knowledge and decisions; index searches + actual source code. If the index service is unavailable, fall back to `grep -r`. + + ### During Security Audits + 1. Find all usages of unsafe code: + agent index search "unsafe" --mode keyword --language rust --limit 20 --json + 2. Find unwrap/expect calls in non-test code: + agent index search "unwrap expect" --mode keyword --file-pattern "crates/*/src/**" --limit 20 --json + 3. Find authentication and authorization code: + agent index search "authentication authorization middleware" --mode vector --limit 10 --json + 4. Search for SQL query construction: + agent index search "query SQL execute" --mode hybrid --language rust --limit 10 --json + 5. Trace code paths for a specific dependency: + agent index search "" --mode keyword --limit 20 --json + + ### For Targeted Investigations + When investigating a specific security concern from an issue: + agent index search "" --mode hybrid --language rust --limit 10 --json + + ### Search Mode Guide + - Use `--mode keyword` for pattern-based scans (unwrap, unsafe, specific function names) + - Use `--mode hybrid` for broader security concept searches + - Use `--mode vector` for conceptual queries ("input validation", "secret management") + - Add `--file-pattern "crates/*/src/api*"` to focus on HTTP handler code diff --git a/.agentd/agents/tester.yml b/.agentd/agents/tester.yml index 243d02f3..7681e231 100644 --- a/.agentd/agents/tester.yml +++ b/.agentd/agents/tester.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/agent.yml + # Tester agent — systematic test coverage for the agentd workspace. # # Triggered when an issue is labeled "test-agent". Identifies undertested @@ -32,6 +34,8 @@ tool_policy: - "Edit(crates/*/tests/**)" - "Write(.github/workflows/**)" - "Edit(.github/workflows/**)" + sandbox_bypass: + - "Bash(agent *)" rooms: - engineering @@ -46,6 +50,7 @@ env: AGENTD_WRAP_SERVICE_URL: http://localhost:7005 AGENTD_ORCHESTRATOR_SERVICE_URL: http://localhost:7006 AGENTD_MEMORY_SERVICE_URL: http://localhost:7008 + AGENTD_INDEX_SERVICE_URL: http://localhost:7012 AGENTD_COMMUNICATE_SERVICE_URL: http://localhost:7010 system_prompt: | diff --git a/.agentd/agents/triage.yml b/.agentd/agents/triage.yml index 60b5e7b2..b0684866 100644 --- a/.agentd/agents/triage.yml +++ b/.agentd/agents/triage.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/agent.yml + # Triage agent — automatic issue processing for the agentd project. # # Triggered when an issue is labeled "needs-triage". Assigns labels, @@ -27,6 +29,7 @@ env: AGENTD_WRAP_SERVICE_URL: http://localhost:7005 AGENTD_ORCHESTRATOR_SERVICE_URL: http://localhost:7006 AGENTD_MEMORY_SERVICE_URL: http://localhost:7008 + AGENTD_INDEX_SERVICE_URL: http://localhost:7012 AGENTD_COMMUNICATE_SERVICE_URL: http://localhost:7010 system_prompt: | @@ -167,7 +170,7 @@ system_prompt: | | Milestone | Assign when | |----------------------|--------------------------------------------------------------------| | v0.3.0 Stabilization | Bug fixes, code quality, common crate consolidation, docs | - | v0.4.0 Complete Impls | hook, monitor, ollama crate work; BAML integration | + | v0.4.0 Complete Impls | hook, monitor, ollama crate work | | v0.4.2 Extensibility | New task sources, integration tests, server-side improvements | | v0.9.0 Autonomous Pipeline | Agent/workflow YAML changes, git-spice, conductor, specialist agents | | v1.0.0 Production | Auth, config management, deployment, sandboxing | @@ -318,3 +321,19 @@ system_prompt: | ### What NOT to Remember - Issue-specific content already in GitHub comments - Standard label definitions already in the label reference above + + ## Code Index Protocol + + You have access to a semantic code search service via the `agent index` CLI. + Use it to improve triage accuracy by understanding which code areas an issue + affects. If the index service is unavailable, skip this step. + + ### During Triage + 1. Estimate complexity by searching for the affected code area: + agent index search "" --mode vector --language rust --limit 5 --json + Count the number of files and symbols returned to inform your complexity estimate. + 2. Verify which crates are affected: + agent index search "" --mode hybrid --limit 10 --json + Check the file_path field in results to identify affected crates. + 3. For duplicate detection, compare code areas affected by candidate duplicates. + If two issues map to the same files via index search, they may conflict. diff --git a/.agentd/agents/worker.yml b/.agentd/agents/worker.yml index 71b273ca..7bb7b71b 100644 --- a/.agentd/agents/worker.yml +++ b/.agentd/agents/worker.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/agent.yml + # Worker agent — receives GitHub issues as tasks and implements them. # # Usage: @@ -27,6 +29,15 @@ tool_policy: - "Bash(git push --force*)" # Block direct deletion of issues or milestones — always human - "Bash(gh issue delete *)" + # Bypass the Claude Code sandbox for commands that require direct TLS to + # GitHub. The sandbox intercepts outbound HTTPS with its own proxy cert, + # which causes x509 failures for git-spice and gh when talking to the + # GitHub GraphQL / REST APIs. + sandbox_bypass: + - "Bash(agent *)" + - "Bash(git-spice branch submit *)" + - "Bash(git-spice repo sync*)" + - "Bash(gh pr create *)" rooms: - engineering @@ -41,6 +52,7 @@ env: AGENTD_WRAP_SERVICE_URL: http://localhost:7005 AGENTD_ORCHESTRATOR_SERVICE_URL: http://localhost:7006 AGENTD_MEMORY_SERVICE_URL: http://localhost:7008 + AGENTD_INDEX_SERVICE_URL: http://localhost:7012 AGENTD_COMMUNICATE_SERVICE_URL: http://localhost:7010 system_prompt: | @@ -108,10 +120,17 @@ system_prompt: | ### Submitting a Pull Request + Always use explicit `--title` and `--body` so that `Closes #N` appears on + its own line — GitHub only auto-links issues when the closing keyword is on + a dedicated line, not embedded inline (e.g. `(closes #N)` does NOT work): + ```bash - # Submit the current branch as a PR (creates or updates — idempotent) - # Add --label review-agent when the PR is ready for automated review - git-spice branch submit --fill --no-prompt --label review-agent + # Replace with the issue number, and <body> with real content + git-spice branch submit --no-prompt --label review-agent \ + --title "feat: <title>" \ + --body "<one-sentence summary of what was implemented> + + Closes #<N>" ``` ### Handling Reviewer Change Requests @@ -122,8 +141,12 @@ system_prompt: | # Make the required code changes, then amend the commit git-spice commit amend -m "feat(scope): description (address review feedback)" - # Resubmit — this updates the existing PR - git-spice branch submit --fill --no-prompt --label review-agent + # Resubmit — this updates the existing PR (keep Closes #N in the body) + git-spice branch submit --no-prompt --label review-agent \ + --title "feat: <title>" \ + --body "<summary of changes addressing review feedback> + + Closes #<N>" ``` ### Session Start @@ -193,3 +216,34 @@ system_prompt: | ### What NOT to Remember - Information already in code, comments, or git history - Ephemeral task state (use your conversation context for that) + + ## Code Index Protocol + + You have access to a semantic code search service via the `agent index` CLI. + Use it to discover existing code before writing new code. The index complements + memory: memory stores human knowledge and decisions; index searches actual source + code. If the index service is unavailable, fall back to `grep -r`. + + ### On Startup + Before beginning any task, search the codebase for relevant existing code: + 1. Search for code related to the issue topic: + agent index search "<issue topic or feature area>" --mode hybrid --language rust --limit 10 --json + 2. If the issue mentions specific types or functions, look them up: + agent index search "<TypeName or function_name>" --mode keyword --hierarchy symbol --limit 5 --json + + ### Before Each Task + Search for code in the area you are about to modify: + agent index search "<module or feature being changed>" --mode hybrid --file-pattern "crates/<relevant-crate>/src/**" --limit 10 --json + + ### When to Use Index Search + - Before creating a new type, struct, or function: search to ensure it does not already exist + - Before implementing a pattern: search for existing implementations to follow the same style + - When an issue mentions a specific crate or module: search to understand the current state + - When you need to find all call sites of a function or usages of a type + + ### Search Mode Guide + - Use `--mode hybrid` for most queries (combines semantic + keyword matching) + - Use `--mode keyword` for exact identifier lookups (function names, type names) + - Use `--mode vector` for conceptual queries ("how does authentication work") + - Add `--hierarchy symbol` to find specific functions/structs/traits + - Add `--file-pattern "crates/<crate>/src/**"` to scope searches to a specific crate diff --git a/.agentd/rooms/announcements.yml b/.agentd/rooms/announcements.yml index 2bd0a898..44971de1 100644 --- a/.agentd/rooms/announcements.yml +++ b/.agentd/rooms/announcements.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/room.yml + # .agentd/rooms/announcements.yml # # This is a room for announcements and other important information. diff --git a/.agentd/rooms/engineering.yml b/.agentd/rooms/engineering.yml index db4c2e8c..4db71f85 100644 --- a/.agentd/rooms/engineering.yml +++ b/.agentd/rooms/engineering.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/room.yml + # .agentd/rooms/engineering.yml # # Engineering coordination room. diff --git a/.agentd/rooms/operations.yml b/.agentd/rooms/operations.yml index 58f072eb..c7c8c701 100644 --- a/.agentd/rooms/operations.yml +++ b/.agentd/rooms/operations.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/room.yml + # .agentd/rooms/operations.yml # # Operations room for pipeline status digests, merge results, conflict diff --git a/.agentd/rooms/security.yml b/.agentd/rooms/security.yml index 1eae0541..cc580966 100644 --- a/.agentd/rooms/security.yml +++ b/.agentd/rooms/security.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/room.yml + # .agentd/rooms/security.yml # # Security room for audit findings, RUSTSEC advisories, and vulnerability diff --git a/.agentd/workflows/conductor-sync-manual.yml b/.agentd/workflows/conductor-sync-manual.yml index 832ccb5e..ef4e2e94 100644 --- a/.agentd/workflows/conductor-sync-manual.yml +++ b/.agentd/workflows/conductor-sync-manual.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/workflow.yml + # Conductor sync manual workflow — label-triggered pipeline orchestration. # # Triggered when the "conductor-sync" label is applied to any open issue. diff --git a/.agentd/workflows/conductor-sync.yml b/.agentd/workflows/conductor-sync.yml index a7fc734b..ff8864d9 100644 --- a/.agentd/workflows/conductor-sync.yml +++ b/.agentd/workflows/conductor-sync.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/workflow.yml + # Conductor sync workflow — pipeline orchestration heartbeat. # # Runs every 5 minutes. Dispatches the conductor agent to check pipeline diff --git a/.agentd/workflows/design-worker.yml b/.agentd/workflows/design-worker.yml index 7ddf17ac..b16cef0a 100644 --- a/.agentd/workflows/design-worker.yml +++ b/.agentd/workflows/design-worker.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/workflow.yml + # Design workflow — dispatches design issues to the designer agent. # # Issues with the "design-agent" label are picked up automatically. diff --git a/.agentd/workflows/docs-worker.yml b/.agentd/workflows/docs-worker.yml index 93335b50..a05e02a2 100644 --- a/.agentd/workflows/docs-worker.yml +++ b/.agentd/workflows/docs-worker.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/workflow.yml + # Documentation workflow — dispatches documentation issues to the documenter agent. # # Issues with the "docs-agent" label are picked up automatically. diff --git a/.agentd/workflows/enrichment-worker.yml b/.agentd/workflows/enrichment-worker.yml index 494c9c16..b6aac0a5 100644 --- a/.agentd/workflows/enrichment-worker.yml +++ b/.agentd/workflows/enrichment-worker.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/workflow.yml + # Enrichment worker workflow — dispatches issue enrichment to the enricher agent. # # Triggered when an issue has the "enrich-agent" label applied. The enricher @@ -40,6 +42,10 @@ prompt_template: | 2. Search for codebase patterns or past enrichments: agent memory search "enricher issue {{source_id}}" --as-actor enricher --limit 3 --json 3. Review results and build on existing findings rather than duplicating prior analysis. + 4. Search the code index to find relevant source files: + agent index search "{{title}}" --mode vector --language rust --limit 10 --json + 5. Use index results to populate the "Relevant Files" and "Implementation Hints" + sections of your enrichment comment with verified file paths and symbol names. ## IMPORTANT CONSTRAINTS diff --git a/.agentd/workflows/epic-issue-worker.yml b/.agentd/workflows/epic-issue-worker.yml new file mode 100644 index 00000000..c76932fd --- /dev/null +++ b/.agentd/workflows/epic-issue-worker.yml @@ -0,0 +1,243 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/workflow.yml + +# Epic issue worker workflow — picks up epic issues and implements all +# sub-issues in dependency order using stacked branches. +# +# Usage: +# agent apply .agentd/workflows/epic-issue-worker.yml +# agent apply .agentd/ # creates all agents + workflows together + +name: epic-issue-worker +agent: worker + +source: + type: github_issues + owner: geoffjay + repo: agentd + labels: + - epic-agent + state: open + +poll_interval: 120 +enabled: true + +prompt_template: | + Work on the following epic issue by implementing all of its sub-issues + in dependency order using stacked branches. + + Epic #{{source_id}}: {{title}} + URL: {{url}} + Labels: {{labels}} + + Description: + {{body}} + + ## Step 0 — Gather context from memory + + Before starting implementation, search shared memory for relevant context: + 1. Search for memories related to this epic's topic: + agent memory search "{{title}}" --as-actor worker --limit 5 --json + 2. Search for known decisions or gotchas in related areas: + agent memory search "epic {{source_id}}" --as-actor worker --limit 3 --json + 3. Review results and factor relevant findings into your plan. + 4. Search the code index for code areas the epic will affect: + agent index search "{{title}}" --mode hybrid --language rust --limit 10 --json + 5. Review code index results to understand the scope of changes across crates. + + ## Step 1 — Fetch sub-issues and build dependency graph + + Fetch all sub-issues of this epic using the GitHub GraphQL API: + + ```bash + gh api graphql -f query=' + query($owner:String!, $repo:String!, $number:Int!) { + repository(owner:$owner, name:$repo) { + issue(number:$number) { + subIssues(first:50) { + nodes { + number + title + state + url + blockedBy(first:20) { + nodes { number title state } + } + blocking(first:20) { + nodes { number title state } + } + } + totalCount + } + subIssuesSummary { total completed percentCompleted } + } + } + }' -f owner=geoffjay -f repo=agentd -F number={{source_id}} + ``` + + From the result: + - Collect all sub-issues that are in `OPEN` state. + - For each sub-issue, record which other sub-issues (within this epic) it + is `blockedBy`. + - Build a topological ordering: issues with no open blockers come first, + then issues whose blockers all appear earlier in the list. + - If the dependency graph has a cycle, stop and comment on the epic: + `gh issue comment {{source_id}} --repo geoffjay/agentd --body "Dependency cycle detected among sub-issues: <details>. Cannot proceed."` + + ## Step 2 — Sync and create the epic root branch + + ```bash + git-spice repo sync + git checkout feature/autonomous-pipeline + git pull origin feature/autonomous-pipeline + git-spice branch create epic-{{source_id}} -m "epic: {{title}}" + ``` + + This creates an empty root branch for the epic. It will serve as the + base for the first sub-issue and will accumulate all work via the stack. + + Submit a PR for the epic root branch against `main` immediately. This PR + will contain no changes initially — it is the rollup PR for the entire epic: + + ```bash + git-spice branch submit --no-prompt \ + --title "epic: {{title}} (#{{source_id}})" \ + --body "Rollup PR for epic #{{source_id}}. + + This branch collects all sub-issue work via stacked PRs. + + Sub-issues will be linked as they are implemented." + ``` + + ## Step 3 — Implement sub-issues in dependency order + + For each sub-issue in topological order, perform the following: + + ### 3a — Determine the stack base + + - If this is the **first** sub-issue (no blockers within the epic): + the base is `epic-{{source_id}}` (the epic root branch). + - If this sub-issue is **blocked by** another sub-issue in the epic: + the base is the branch of the blocking sub-issue it depends on. + When there are multiple blockers, use the one that appears latest in + the topological order (it will already contain the transitive work). + + Check out the base branch before creating the new branch: + + ```bash + git checkout <base-branch> + git-spice branch create issue-<N> -m "feat: <brief description from sub-issue title>" + ``` + + ### 3b — Implement the sub-issue + + 1. Read the sub-issue carefully: + `gh issue view <N> --repo geoffjay/agentd` + 2. Plan your approach. + 3. Implement the change. + 4. Run relevant tests: `cargo test` (for Rust changes). + 5. Ensure `cargo fmt` and `cargo clippy` pass for Rust changes. + 6. Commit: + ```bash + git add <changed-files> + git-spice commit create -m "feat(<scope>): <description>" + ``` + + ### 3c — Submit the sub-issue PR + + Submit a PR for this sub-issue branch with an explicit body so GitHub + links the issue. git-spice automatically targets the correct base branch: + + ```bash + git-spice branch submit --no-prompt --label review-agent \ + --title "feat: <brief description from sub-issue title>" \ + --body "<one-sentence summary of what was implemented> + + Closes #<N>" + ``` + + ### 3d — Move to the next sub-issue + + Repeat steps 3a-3c for the next sub-issue in the topological order. + Each subsequent branch stacks on top of the previous one (or on its + dependency's branch if the graph is not a simple chain). + + ## Step 4 — Final stack submission and cleanup + + After all sub-issues are implemented, verify the full stack: + + ```bash + git-spice log short + ``` + + Confirm that the stack looks like: + + ``` + feature/autonomous-pipeline + epic-{{source_id}} → PR to main + issue-<first> → PR to epic-{{source_id}} + issue-<second> → PR to issue-<first> + ... → PR to previous + ``` + + If any PRs were not submitted during the per-issue step (e.g. due to + an error), submit each remaining branch individually with an explicit body: + + ```bash + # For each unsubmitted branch (replace <N> with the sub-issue number): + git checkout issue-<N> + git-spice branch submit --no-prompt --label review-agent \ + --title "feat: <brief description>" \ + --body "<one-sentence summary> + + Closes #<N>" + ``` + + ## Step 5 — Store findings and close the epic + + Store any significant findings discovered during implementation: + ```bash + agent memory remember "<what you learned>" \ + --created-by worker --type information \ + --tags worker,epic-{{source_id}} --visibility public + ``` + + Comment on the epic with a summary of the work: + ```bash + gh issue comment {{source_id}} --repo geoffjay/agentd --body \ + "All sub-issues implemented and PRs submitted as a stacked branch chain. + Stack root: epic-{{source_id}} → main + Sub-issue PRs submitted with review-agent label." + ``` + + Do NOT close the epic issue — it remains open until all sub-issue PRs + are merged. The conductor will close it during merge queue processing. + + ## Error Handling + + - If a sub-issue is unclear or too large, comment on it with questions + and skip it. Continue with the next sub-issue that is not blocked by it. + - If `cargo test`, `cargo fmt`, or `cargo clippy` fails, fix the issue + before committing. If the failure is in unrelated code, note it in a + comment on the sub-issue and proceed. + - If a rebase conflict occurs during branch creation, follow the + conflict resolution protocol from the git-spice skill. + + ## Handling needs-rework (reviewer requested changes) + + If re-dispatched because a sub-issue PR has the "needs-rework" label: + 1. Fetch the review comments for the flagged PR. + 2. Check out the sub-issue branch. + 3. Address the feedback. + 4. Amend and resubmit: + ```bash + git-spice commit amend -m "feat(<scope>): <description> (address review feedback)" + git-spice branch submit --no-prompt --label review-agent \ + --title "feat: <description>" \ + --body "<summary of changes addressing review feedback> + + Closes #<N>" + ``` + 5. Restack any branches above: + ```bash + git-spice upstack restack + ``` diff --git a/.agentd/workflows/examples/README.md b/.agentd/workflows/examples/README.md new file mode 100644 index 00000000..95377cf6 --- /dev/null +++ b/.agentd/workflows/examples/README.md @@ -0,0 +1,108 @@ +# Ask Service Example Workflows + +This directory contains example workflow pairs demonstrating the agent-to-human Q&A pattern using the redesigned ask service. + +Each example consists of two workflows: +1. **Ask workflow** — an agent creates a question for the human user +2. **Response workflow** — fires automatically when the human answers, via `ask_response` trigger + +## Examples + +### 1. Daily Nutrition Check + +**Files:** `daily-nutrition-ask.yml` + `daily-nutrition-analyze.yml` + +**Pattern:** Cron → Ask → ask_response → Analysis + +The dietician agent asks the user what they ate each morning at 10am. When +the user answers, the response workflow automatically analyzes their dietary +intake and provides personalized recommendations. + +**Trigger chain:** +``` +Cron (10am daily) → dietician asks nutrition question + → user answers via CLI or UI + → ask_response fires → dietician analyzes and notifies +``` + +### 2. Deployment Approval Gate + +**Files:** `deploy-approval-ask.yml` + `deploy-approval-respond.yml` + +**Pattern:** Manual → Ask (urgent) → ask_response → Deploy or Abort + +The deploy agent requests explicit human approval before executing a +production deployment. The operator's YES/NO response is handled +automatically — proceeding with or aborting the deployment accordingly. + +**Trigger chain:** +``` +Manual dispatch → deploy agent asks urgent approval question + → operator answers YES or NO + → ask_response fires → deploy or abort +``` + +### 3. End-of-Day Productivity Review + +**Files:** `productivity-check.yml` + `productivity-followup.yml` + +**Pattern:** Cron (weekdays) → Ask → ask_response → Summary + Plan + +The productivity agent asks the user a reflective end-of-day question each +weekday at 4pm. When the user answers, the response workflow synthesizes a +summary of wins and blockers and suggests tomorrow's priorities. + +**Trigger chain:** +``` +Cron (4pm Mon-Fri) → productivity agent asks review question + → user answers at their convenience + → ask_response fires → agent delivers summary + tomorrow's plan +``` + +## Quick Start + +Deploy a workflow pair: + +```bash +# Deploy both workflows in a pair together +agent apply .agentd/workflows/examples/daily-nutrition-ask.yml +agent apply .agentd/workflows/examples/daily-nutrition-analyze.yml + +# Enable them via the API +agent orchestrator enable-workflow <daily-nutrition-ask-id> +agent orchestrator enable-workflow <daily-nutrition-analyze-id> +``` + +All example workflows ship with `enabled: false` to prevent accidental +activation. Enable them explicitly after reviewing the configuration. + +## ask_response Trigger Reference + +The `ask_response` trigger fires when any question matching the filter criteria +is answered or dismissed. All filters are optional. + +```yaml +source: + type: ask_response + agent_id: my-agent # optional: only react to this agent's questions + category: health # optional: only react to this category + response_pattern: "yes|approve" # optional: regex match on answer text +``` + +### Template Variables + +| Variable | Description | +|---|---| +| `{{question_id}}` | UUID of the answered question | +| `{{agent_id}}` | Agent that asked the question | +| `{{category}}` | Question category (if set) | +| `{{question}}` | The original question text | +| `{{answer}}` | The human's answer (empty if dismissed) | +| `{{event_type}}` | `"answered"` or `"dismissed"` | +| `{{workflow_id}}` | Workflow that created the question (if set) | + +## Further Reading + +- Agent skill: `.claude/skills/agent-ask/SKILL.md` +- Ask service API: `crates/ask/src/api.rs` +- Trigger strategy: `crates/orchestrator/src/scheduler/strategy.rs` diff --git a/.agentd/workflows/examples/daily-nutrition-analyze.yml b/.agentd/workflows/examples/daily-nutrition-analyze.yml new file mode 100644 index 00000000..2a4b5019 --- /dev/null +++ b/.agentd/workflows/examples/daily-nutrition-analyze.yml @@ -0,0 +1,58 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/workflow.yml + +# Daily Nutrition Analyze workflow — Part 2 of 2. +# +# Fires automatically when the user answers a health-category question from the +# dietician agent (via the ask_response trigger). Receives the question and +# answer as template variables, then prompts the dietician to analyze dietary +# intake and provide recommendations. +# +# This is the response handler for daily-nutrition-ask.yml. +# +# Pattern: Ask Response → Agent → Analysis + Recommendations +# +# Usage: +# agent apply .agentd/workflows/examples/daily-nutrition-ask.yml +# agent apply .agentd/workflows/examples/daily-nutrition-analyze.yml + +name: daily-nutrition-analyze +agent: dietician + +source: + type: ask_response + agent_id: dietician # Only react to questions asked by the dietician agent + category: health # Only react to health-category questions + +poll_interval: 60 +enabled: false # Set to true after deploying both workflows in this pair + +prompt_template: | + The user has responded to your nutrition question. Here are the details: + + Question ID: {{question_id}} + Event Type: {{event_type}} + Question: {{question}} + Answer: {{answer}} + + {{#if (eq event_type "dismissed")}} + The user dismissed the question without answering. Send a friendly reminder + that nutrition tracking helps with their health goals, and let them know they + can answer tomorrow's check-in at their convenience. + {{else}} + Analyze their dietary intake and provide: + + 1. **Estimated calories** — rough breakdown by meal + 2. **Macro summary** — estimated protein, carbs, and fat + 3. **Nutritional highlights** — what they did well + 4. **One recommendation** — a single, actionable suggestion for today + + Keep the response encouraging and conversational. Use the notify service to + deliver the analysis: + + ```bash + agent notify create \ + --title "Your Nutrition Analysis" \ + --message "<your analysis here>" \ + --priority normal + ``` + {{/if}} diff --git a/.agentd/workflows/examples/daily-nutrition-ask.yml b/.agentd/workflows/examples/daily-nutrition-ask.yml new file mode 100644 index 00000000..49e5b543 --- /dev/null +++ b/.agentd/workflows/examples/daily-nutrition-ask.yml @@ -0,0 +1,46 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/workflow.yml + +# Daily Nutrition Ask workflow — Part 1 of 2. +# +# Fires every morning at 10am and prompts the dietician agent to ask the user +# what they ate the previous day. The agent uses the ask service to create a +# structured Q&A question that the human answers asynchronously. +# +# Pair this with daily-nutrition-analyze.yml, which fires automatically via +# the ask_response trigger when the human answers the question. +# +# Pattern: Cron → Agent → Ask Question +# +# Usage: +# agent apply .agentd/workflows/examples/daily-nutrition-ask.yml +# agent apply .agentd/workflows/examples/daily-nutrition-analyze.yml + +name: daily-nutrition-ask +agent: dietician + +source: + type: cron + expression: "0 10 * * *" # Daily at 10:00 AM + +poll_interval: 60 +enabled: false # Set to true after deploying both workflows in this pair + +prompt_template: | + Good morning! It's time for the daily nutrition check-in. + + Ask the user what they ate yesterday by creating a question through the ask service: + + ```bash + agent ask create \ + --agent-id dietician \ + --question "What did you eat yesterday? Please include breakfast, lunch, dinner, and any snacks." \ + --category health \ + --priority normal \ + --context "Daily nutrition tracking — I'll analyze your intake and provide personalized recommendations." \ + --expires-in 86400 + ``` + + After creating the question, note its ID and let the user know you're waiting for their response. + The daily-nutrition-analyze workflow will automatically fire when they answer. + + Do not poll for the answer — it will come via the ask_response trigger. diff --git a/.agentd/workflows/examples/deploy-approval-ask.yml b/.agentd/workflows/examples/deploy-approval-ask.yml new file mode 100644 index 00000000..e3745620 --- /dev/null +++ b/.agentd/workflows/examples/deploy-approval-ask.yml @@ -0,0 +1,56 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/workflow.yml + +# Deploy Approval Ask workflow — Part 1 of 2. +# +# Triggered manually (or chained from a CI/CD dispatch_result) when a release +# is ready. The deploy agent asks the human operator for explicit approval +# before proceeding. The operator's answer triggers deploy-approval-respond.yml. +# +# Pattern: Manual / dispatch_result → Agent → Ask Approval Question +# +# Usage (manual dispatch via CLI): +# agent orchestrator dispatch <workflow-id> "Release v2.3.0 is ready. Request approval." +# +# Or chain from a CI success dispatch_result trigger: +# source: +# type: dispatch_result +# status: completed +# source_workflow_id: "<ci-workflow-uuid>" + +name: deploy-approval-ask +agent: deploy + +source: + type: manual + +poll_interval: 60 +enabled: false + +prompt_template: | + A deployment approval is needed. Ask the operator for explicit go/no-go + sign-off before proceeding. + + Create an urgent approval question through the ask service: + + ```bash + agent ask create \ + --agent-id deploy \ + --question "Release v2.3.0 is staged and ready. Do you approve the production deployment? Reply YES to proceed or NO to abort." \ + --category deployment \ + --priority urgent \ + --context "All CI checks passed. Staging environment validated. Estimated deployment time: 10 minutes." \ + --expires-in 3600 + ``` + + After creating the question, send a notification so the operator is alerted: + + ```bash + agent notify create \ + --title "Deployment Approval Required" \ + --message "Release v2.3.0 is staged and awaiting your go/no-go. Check the ask service to approve or abort." \ + --priority urgent \ + --requires-response + ``` + + Do not proceed with deployment yet. The deploy-approval-respond workflow + will fire automatically once the operator responds. diff --git a/.agentd/workflows/examples/deploy-approval-respond.yml b/.agentd/workflows/examples/deploy-approval-respond.yml new file mode 100644 index 00000000..05452488 --- /dev/null +++ b/.agentd/workflows/examples/deploy-approval-respond.yml @@ -0,0 +1,72 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/workflow.yml + +# Deploy Approval Respond workflow — Part 2 of 2. +# +# Fires automatically when the operator answers or dismisses a deployment- +# category question from the deploy agent. Checks the response pattern to +# determine whether to proceed with or abort the deployment. +# +# A response matching YES/yes/y/approve proceeds with deployment. +# Any other response (NO, dismiss, etc.) aborts safely. +# +# Pattern: Ask Response → Agent → Deploy or Abort +# +# Usage: +# agent apply .agentd/workflows/examples/deploy-approval-ask.yml +# agent apply .agentd/workflows/examples/deploy-approval-respond.yml + +name: deploy-approval-respond +agent: deploy + +source: + type: ask_response + agent_id: deploy # Only react to questions asked by the deploy agent + category: deployment # Only react to deployment-category questions + +poll_interval: 60 +enabled: false + +prompt_template: | + The operator has responded to a deployment approval request. + + Question ID: {{question_id}} + Event Type: {{event_type}} + Question: {{question}} + Answer: {{answer}} + + Evaluate the response and take action: + + If the answer is "YES", "yes", "y", "approve", or "approved" — proceed: + 1. Run the deployment pipeline (e.g. trigger the deploy script or CI job) + 2. Notify the operator: + ```bash + agent notify create \ + --title "Deployment Approved — Proceeding" \ + --message "Operator approved. Deployment of v2.3.0 is now underway." \ + --priority high + ``` + 3. Monitor for completion and notify again when done. + + If the answer is "NO", "no", "abort", or "cancel" — abort: + 1. Do NOT proceed with deployment + 2. Notify the operator: + ```bash + agent notify create \ + --title "Deployment Aborted" \ + --message "Operator declined. Deployment of v2.3.0 has been cancelled." \ + --priority normal + ``` + + If the question was dismissed (event_type = "dismissed") — treat as abort: + 1. Do NOT proceed with deployment + 2. Notify the operator that the window expired or was dismissed. + + If the answer is ambiguous — ask for clarification: + ```bash + agent ask create \ + --agent-id deploy \ + --question "Your response was unclear. Please reply YES to deploy or NO to abort." \ + --category deployment \ + --priority urgent \ + --expires-in 1800 + ``` diff --git a/.agentd/workflows/examples/productivity-check.yml b/.agentd/workflows/examples/productivity-check.yml new file mode 100644 index 00000000..a5498c1b --- /dev/null +++ b/.agentd/workflows/examples/productivity-check.yml @@ -0,0 +1,44 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/workflow.yml + +# Productivity Check workflow — Part 1 of 2. +# +# Fires every weekday afternoon at 4pm and prompts the productivity agent to +# ask the user about their day — what they accomplished, what blocked them, +# and how they're feeling. The response triggers productivity-followup.yml +# which provides a summary and suggestions for tomorrow. +# +# Pattern: Cron (weekdays) → Agent → Ask End-of-Day Review +# +# Usage: +# agent apply .agentd/workflows/examples/productivity-check.yml +# agent apply .agentd/workflows/examples/productivity-followup.yml + +name: productivity-check +agent: productivity + +source: + type: cron + expression: "0 16 * * 1-5" # Weekdays (Mon-Fri) at 4:00 PM + +poll_interval: 60 +enabled: false + +prompt_template: | + It's end-of-day check-in time. Ask the user about their productivity today. + + Create a conversational end-of-day review question: + + ```bash + agent ask create \ + --agent-id productivity \ + --question "How did your day go? What did you accomplish today, what got in the way, and how are you feeling?" \ + --category productivity \ + --priority low \ + --context "End-of-day review — your answers help me identify patterns and suggest improvements for tomorrow." \ + --expires-in 43200 + ``` + + The question expires in 12 hours so it doesn't carry over to the next morning. + + Do not wait for the response — the productivity-followup workflow will handle + it automatically when the user answers. diff --git a/.agentd/workflows/examples/productivity-followup.yml b/.agentd/workflows/examples/productivity-followup.yml new file mode 100644 index 00000000..15e445e1 --- /dev/null +++ b/.agentd/workflows/examples/productivity-followup.yml @@ -0,0 +1,62 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/workflow.yml + +# Productivity Followup workflow — Part 2 of 2. +# +# Fires when the user answers their end-of-day productivity review question. +# The productivity agent synthesizes a brief summary, identifies any blockers, +# and provides a focused plan for tomorrow. +# +# Pattern: Ask Response → Agent → Summary + Tomorrow's Plan +# +# Usage: +# agent apply .agentd/workflows/examples/productivity-check.yml +# agent apply .agentd/workflows/examples/productivity-followup.yml + +name: productivity-followup +agent: productivity + +source: + type: ask_response + agent_id: productivity # Only react to questions from the productivity agent + category: productivity # Only react to productivity-category questions + +poll_interval: 60 +enabled: false + +prompt_template: | + The user has completed their end-of-day review. Here are the details: + + Question ID: {{question_id}} + Event Type: {{event_type}} + Question: {{question}} + Answer: {{answer}} + + {{#if (eq event_type "dismissed")}} + The user skipped today's review — that's okay. Send a brief, non-judgmental + acknowledgment: + + ```bash + agent notify create \ + --title "Check-in Skipped" \ + --message "No worries — see you tomorrow for another check-in!" \ + --priority low + ``` + {{else}} + Based on their response, provide a brief end-of-day summary with: + + 1. **Wins** — what they accomplished (even small things count) + 2. **Blockers** — any obstacles worth addressing (if mentioned) + 3. **Tomorrow's focus** — one or two concrete priorities based on what they shared + + Keep it short, warm, and encouraging. Deliver via notification: + + ```bash + agent notify create \ + --title "End-of-Day Summary" \ + --message "<your summary here>" \ + --priority low + ``` + + If they mentioned being overwhelmed or stressed, acknowledge that first + before jumping to productivity suggestions. + {{/if}} diff --git a/.agentd/workflows/issue-worker.yml b/.agentd/workflows/issue-worker.yml index a8e1aedc..cf4ed1ec 100644 --- a/.agentd/workflows/issue-worker.yml +++ b/.agentd/workflows/issue-worker.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/workflow.yml + # Issue worker workflow — polls GitHub issues and dispatches them to the worker agent. # # Usage: @@ -35,6 +37,11 @@ prompt_template: | 2. Search for known decisions or gotchas in related areas: agent memory search "issue {{source_id}}" --as-actor worker --limit 3 --json 3. Review results and factor relevant findings into your plan. + 4. Search the code index for source code related to this issue: + agent index search "{{title}}" --mode hybrid --language rust --limit 10 --json + 5. If the issue mentions specific types or functions, look them up: + agent index search "<identifier from issue>" --mode keyword --hierarchy symbol --limit 5 --json + 6. Review both memory and index results before planning your approach. Instructions: 1. Move the issue to "In Progress" by assigning it using the status field @@ -49,16 +56,26 @@ prompt_template: | 4. Implement the changes described in the issue 5. Run tests to verify: cargo test 6. Ensure cargo fmt and cargo clippy pass for Rust changes - 7. Commit with a descriptive message referencing the issue: - git-spice commit create -m "feat: <description> (closes #{{source_id}})" - 8. Submit the branch as a PR (creates or updates — idempotent): - git-spice branch submit --fill --no-prompt --label review-agent + 7. Commit with a descriptive message: + git-spice commit create -m "feat: <description>" + 8. Submit the branch as a PR with an explicit body so GitHub links the issue. + Replace <title> and <body> with the actual title and a one-sentence summary: + git-spice branch submit --no-prompt --label review-agent \ + --title "feat: <title>" \ + --body "<one-sentence summary of what was implemented> + + Closes #{{source_id}}" + 9. Store any significant findings or decisions made during implementation: - agent memory remember "<what you learned>" --created-by worker --type information --tags worker,issue-{{source_id}} --visibility public + agent memory remember "<what you learned>" --created-by worker --type information --tags worker,issue-{{source_id}} --visibility public 10. Close the issue: gh issue close {{source_id}} --repo geoffjay/agentd Handling needs-rework (reviewer requested changes): If this issue's PR has the "needs-rework" label, the reviewer found issues. Fetch the review comments, address the feedback, then: - git-spice commit amend -m "feat: <description> (address review feedback)" - git-spice branch submit --fill --no-prompt --label review-agent + git-spice commit amend -m "feat: <description> (address review feedback)" + git-spice branch submit --no-prompt --label review-agent \ + --title "feat: <title>" \ + --body "<summary of changes made> + + Closes #{{source_id}}" diff --git a/.agentd/workflows/merge-worker.yml b/.agentd/workflows/merge-worker.yml index d0718d65..0dabf6a1 100644 --- a/.agentd/workflows/merge-worker.yml +++ b/.agentd/workflows/merge-worker.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/workflow.yml + # Merge worker workflow — dispatches merge automation to the conductor agent. # # Triggered when a pull request in geoffjay/agentd has the "merge-ready" label diff --git a/.agentd/workflows/plan-worker.yml b/.agentd/workflows/plan-worker.yml index 535bbd03..0195abea 100644 --- a/.agentd/workflows/plan-worker.yml +++ b/.agentd/workflows/plan-worker.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/workflow.yml + # Plan worker workflow — polls GitHub issues and dispatches them to the worker agent. # # Note: this workflow prompt uses an gh extension that is installed with: @@ -38,6 +40,9 @@ prompt_template: | 2. Search for prior planning decisions and architectural context: agent memory search "architecture decisions" --as-actor planner --tags planner --limit 5 --json 3. Review results to avoid duplicating past work and build on existing decisions. + 4. Search the code index to understand the current state of relevant code: + agent index search "{{title}}" --mode vector --language rust --limit 10 --json + 5. Use index results to verify assumptions about what already exists before proposing issues. Instructions: diff --git a/.agentd/workflows/pull-request-reviewer.yml b/.agentd/workflows/pull-request-reviewer.yml index 5dcd53c0..f907df62 100644 --- a/.agentd/workflows/pull-request-reviewer.yml +++ b/.agentd/workflows/pull-request-reviewer.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/workflow.yml + # PR reviewer workflow — polls pull requests and dispatches reviews when labeled. # # Triggers when a PR in geoffjay/agentd has the "review-agent" label applied. @@ -39,7 +41,11 @@ prompt_template: | agent memory search "{{title}}" --as-actor reviewer --limit 5 --json 2. Search for known issues or patterns in the areas being modified: agent memory search "review findings" --as-actor reviewer --tags reviewer --limit 5 --json - 3. Review results to inform your review — look for recurring issues or past decisions. + 3. Review results to inform your review -- look for recurring issues or past decisions. + 4. Search the code index for context around the files being changed: + agent index search "{{title}}" --mode hybrid --language rust --limit 5 --json + 5. After reading the diff, search for callers of any changed public interfaces: + agent index search "<changed function or type>" --mode keyword --hierarchy symbol --limit 10 --json Instructions: 1. Check the stack position of this PR before reviewing: diff --git a/.agentd/workflows/pull-request-rework.yml b/.agentd/workflows/pull-request-rework.yml new file mode 100644 index 00000000..73d891aa --- /dev/null +++ b/.agentd/workflows/pull-request-rework.yml @@ -0,0 +1,69 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/workflow.yml + +# PR rework workflow — polls pull requests and dispatches rework when labeled. +# +# Triggers when a PR in geoffjay/agentd has the "needs-rework" label applied. +# The worker reads reviewer feedback, addresses it, and resubmits for review. +# +# Usage: +# agent apply .agentd/workflows/pull-request-rework.yml +# agent apply .agentd/ # creates agent + workflow together + +name: pull-request-rework +agent: worker + +source: + type: github_pull_requests + owner: geoffjay + repo: agentd + labels: + - needs-rework + state: open + +poll_interval: 60 +enabled: true + +prompt_template: | + Address reviewer feedback on the following pull request: + + PR #{{source_id}}: {{title}} + URL: {{url}} + Labels: {{labels}} + + Description: + {{body}} + + Step 0 — Gather context from memory: + Before addressing feedback, search shared memory for relevant context: + 1. Search for memories related to this PR's topic: + agent memory search "{{title}}" --as-actor worker --limit 5 --json + 2. Search for any prior review findings in this area: + agent memory search "review findings" --as-actor worker --tags reviewer --limit 5 --json + 3. Review results to understand recurring issues or past decisions. + 4. Search the code index for context around the files being changed: + agent index search "{{title}}" --mode hybrid --language rust --limit 5 --json + + Instructions: + 1. Check out the branch for this PR: + gh pr checkout {{source_id}} --repo geoffjay/agentd + 2. Fetch all review comments to understand what needs to be addressed: + gh pr review list {{source_id}} --repo geoffjay/agentd + gh pr view {{source_id}} --repo geoffjay/agentd --comments + 3. Read the full diff to understand the current state of the PR: + gh pr diff {{source_id}} --repo geoffjay/agentd + 4. Address each piece of reviewer feedback in the code + 5. Run tests to verify: cargo test + 6. Ensure cargo fmt and cargo clippy pass for Rust changes + 7. Commit the changes: + git-spice commit create -m "fix: address review feedback" + 8. Resubmit the PR for review and transition labels: + git-spice branch submit --no-prompt --label review-agent \ + --title "{{title}}" \ + --body "{{body}}" + gh pr edit {{source_id}} --repo geoffjay/agentd --remove-label needs-rework + 9. Store any significant findings made during rework: + agent memory remember "<what you learned>" --created-by worker --type information --tags worker,pr-{{source_id}} --visibility public + + Label reference: + - needs-rework — changes requested; worker must address feedback and resubmit + - review-agent — PR ready for review; triggers the reviewer workflow diff --git a/.agentd/workflows/refactor-review.yml b/.agentd/workflows/refactor-review.yml index 0ffa1c08..3418ae2f 100644 --- a/.agentd/workflows/refactor-review.yml +++ b/.agentd/workflows/refactor-review.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/workflow.yml + # Refactor review workflow — periodic codebase scan for refactor opportunities. # # Runs on a weekly cron schedule. The refactor agent scans one crate per run, @@ -34,6 +36,8 @@ prompt_template: | 3. Check for existing open refactor issues to avoid duplicates: gh issue list --repo geoffjay/agentd --label refactor --state open \ --json number,title | jq '.[].title' + 4. Search the code index for patterns and duplication in the target crate: + agent index search "<target-crate> patterns" --mode hybrid --file-pattern "crates/<target-crate>/src/**" --limit 15 --json Review Instructions: diff --git a/.agentd/workflows/refactor-worker.yml b/.agentd/workflows/refactor-worker.yml index ecbabd1c..bdd3ccc2 100644 --- a/.agentd/workflows/refactor-worker.yml +++ b/.agentd/workflows/refactor-worker.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/workflow.yml + # Refactor worker workflow — dispatches refactor issues to the refactor agent. # # Triggered when an issue has the "refactor-agent" label applied. The refactor @@ -38,6 +40,10 @@ prompt_template: | 2. Search for known patterns or past decisions: agent memory search "refactor issue {{source_id}}" --as-actor refactor --limit 3 --json 3. Review results and incorporate relevant context into your plan. + 4. Search the code index for all usages of the code being refactored: + agent index search "{{title}}" --mode keyword --hierarchy symbol --limit 20 --json + 5. Find similar patterns across the codebase that may need consistent refactoring: + agent index search "<pattern being changed>" --mode hybrid --language rust --limit 10 --json Instructions (Execution Mode): 1. Read the issue body carefully — it describes the current state, desired diff --git a/.agentd/workflows/research-worker.yml b/.agentd/workflows/research-worker.yml index 58aefb9f..49cb8247 100644 --- a/.agentd/workflows/research-worker.yml +++ b/.agentd/workflows/research-worker.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/workflow.yml + # Research worker workflow — dispatches research tasks to the research agent. # # Triggered when an issue has the "research-agent" label applied. The research @@ -40,6 +42,8 @@ prompt_template: | agent memory search "issue {{source_id}}" --as-actor research --limit 3 --json 3. Review results and build on existing findings rather than re-investigating what is already known. + 4. Search the code index for existing implementations related to the research topic: + agent index search "{{title}}" --mode vector --language rust --limit 10 --json Instructions: diff --git a/.agentd/workflows/review-merge-chain.yml b/.agentd/workflows/review-merge-chain.yml index c6c7c70e..422c94e1 100644 --- a/.agentd/workflows/review-merge-chain.yml +++ b/.agentd/workflows/review-merge-chain.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/workflow.yml + # Review → Merge chain workflow. # # Triggered when any workflow dispatch completes successfully. The conductor diff --git a/.agentd/workflows/security-audit.yml b/.agentd/workflows/security-audit.yml index 44548caa..e1ec5736 100644 --- a/.agentd/workflows/security-audit.yml +++ b/.agentd/workflows/security-audit.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/workflow.yml + # Security audit workflow — weekly scheduled security scan. # # Runs every Sunday at 00:00 UTC. The security agent performs a full audit: @@ -28,6 +30,10 @@ prompt_template: | 2. Check for already-open security issues to avoid duplicates: gh issue list --repo geoffjay/agentd --label security --state open \ --json number,title | jq '.[].title' + 3. Use the code index to find security-sensitive code patterns: + agent index search "unsafe unwrap expect" --mode keyword --file-pattern "crates/*/src/**" --limit 20 --json + 4. Search for authentication and input validation code: + agent index search "authentication validation middleware" --mode vector --limit 10 --json Audit Instructions: diff --git a/.agentd/workflows/security-worker.yml b/.agentd/workflows/security-worker.yml index 8c3b34b6..215299e3 100644 --- a/.agentd/workflows/security-worker.yml +++ b/.agentd/workflows/security-worker.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/workflow.yml + # Security worker workflow — on-demand security audit via label trigger. # # Triggered when an issue has the "security-agent" label applied. The security @@ -39,6 +41,10 @@ prompt_template: | 2. Search for known patterns or past audit findings: agent memory search "security issue {{source_id}}" --as-actor security --limit 3 --json 3. Review results and incorporate relevant context into your investigation. + 4. Search the code index for code relevant to the security concern: + agent index search "{{title}}" --mode hybrid --language rust --limit 10 --json + 5. Search for specific security-relevant patterns mentioned in the issue: + agent index search "<vulnerable pattern or crate>" --mode keyword --limit 10 --json Instructions: diff --git a/.agentd/workflows/test-worker.yml b/.agentd/workflows/test-worker.yml index 5acf1772..a8ff3ea6 100644 --- a/.agentd/workflows/test-worker.yml +++ b/.agentd/workflows/test-worker.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/workflow.yml + # Test worker workflow — dispatches test coverage tasks to the tester agent. # # Triggered when an issue has the "test-agent" label applied. The tester agent diff --git a/.agentd/workflows/triage-enrich-chain.yml b/.agentd/workflows/triage-enrich-chain.yml index 7a616dbd..18856a65 100644 --- a/.agentd/workflows/triage-enrich-chain.yml +++ b/.agentd/workflows/triage-enrich-chain.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/workflow.yml + # Triage → Enrich chain workflow. # # Triggered when any workflow dispatch completes successfully. The conductor diff --git a/.agentd/workflows/triage-worker.yml b/.agentd/workflows/triage-worker.yml index 4f000be8..fe3ed358 100644 --- a/.agentd/workflows/triage-worker.yml +++ b/.agentd/workflows/triage-worker.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/geoffjay/agentd/main/schemas/workflow.yml + # Triage worker workflow — dispatches triage tasks to the triage agent. # # Triggered when an issue has the "needs-triage" label applied. The triage @@ -40,6 +42,8 @@ prompt_template: | 2. Search for known triage patterns or label conventions: agent memory search "triage label conventions" --as-actor triage --limit 3 --json 3. Review results and apply labels consistently with past decisions. + 4. Search the code index to estimate which crates are affected: + agent index search "{{title}}" --mode vector --language rust --limit 5 --json Instructions: diff --git a/.cargo/audit.toml b/.cargo/audit.toml index afcb862d..a94aba5a 100644 --- a/.cargo/audit.toml +++ b/.cargo/audit.toml @@ -2,12 +2,16 @@ ignore = [ # Marvin Attack in `rsa` crate - transitive dep via sqlx-mysql, which we don't use "RUSTSEC-2023-0071", - - # CRLs not considered authorative by Distribution Point (rustls-webpki 0.101.7) - # Transitive dep chain: lancedb 0.16 -> aws-smithy-http-client 1.1.10 - # -> rustls 0.21.12 -> rustls-webpki 0.101.7 - # The fix (>=0.103.10) requires the 0.103.x semver series; upgrading requires - # aws-smithy-http-client to adopt rustls 0.22+, which is blocked by lancedb 0.16. - # Track: upgrade lancedb to a release that uses rustls 0.22+. + # `IterMut` unsoundness in lru 0.12.5, pulled in via: + # memory -> lancedb 0.27.1 -> lance 3.0.1 -> tantivy 0.24.2 -> lru 0.12.5 + # The fix requires lru >= 0.13, which requires tantivy to release a new version. + # tantivy main has the fix (uses lru 0.16.3) but it has not been tagged/released. + # Track: re-check when tantivy 0.25+ is published and lance adopts it. + # Refs: https://rustsec.org/advisories/RUSTSEC-2026-0002 + # https://github.com/quickwit-oss/tantivy (main branch) + "RUSTSEC-2026-0002", + # rustls-webpki 0.101.7 CRL matching bug - transitive via lancedb 0.16 -> aws-smithy + # -> hyper-rustls 0.24 -> tokio-rustls 0.24 -> rustls 0.21. Cannot update without + # bumping lancedb past 0.16. Tracked in #543. "RUSTSEC-2026-0049", ] diff --git a/.cargo/config.toml b/.cargo/config.toml index 7d89be09..9e9a97e4 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,6 +1,15 @@ [alias] xtask = "run --package xtask --" +# Uncomment after: brew install sccache +[build] +rustc-wrapper = "sccache" + +# Uncomment after: brew install llvm +# [target.aarch64-apple-darwin] +# linker = "clang" +# rustflags = ["-C", "link-arg=-fuse-ld=/opt/homebrew/opt/llvm/bin/ld64.lld"] + [doc] rustdoc-line-numbers = true rustdoc-preferred-dark-theme = "ayu" diff --git a/baml_src/README.md b/baml_src/README.md deleted file mode 100644 index 89649b08..00000000 --- a/baml_src/README.md +++ /dev/null @@ -1,626 +0,0 @@ -# BAML Integration for agentd - -This directory contains BAML (Basically a Made-up Language) definitions for AI-powered features in agentd. - -## Overview - -BAML provides type-safe LLM function definitions that enable intelligent automation throughout agentd: - -- **Notification Intelligence**: Auto-categorization, digests, and relevance filtering -- **Smart Questions**: Context-aware question generation for the ask service -- **Log Analysis**: Automated error detection and root cause analysis -- **CLI Intelligence**: Natural language command parsing and help -- **Hook Analysis**: Smart filtering of shell events for notifications - -## Project Structure - -``` -baml_src/ -├── README.md # This file -├── clients.baml # LLM client configurations (Ollama, Claude, etc.) -├── generators.baml # Code generation configuration -├── notifications.baml # Notification processing functions -├── questions.baml # Ask service intelligence -├── monitoring.baml # Log analysis and health monitoring -├── cli.baml # Natural language CLI processing -└── hooks.baml # Shell hook event analysis -``` - -## Quick Start - -### Prerequisites - -1. **BAML CLI**: Already installed if you can run `baml --version` -2. **Ollama** (recommended): Local LLM for privacy and cost - ```bash - # Install Ollama: https://ollama.ai - # Pull a model: - ollama pull llama3.2 - ``` -3. **Optional**: Anthropic API key for fallback - ```bash - export ANTHROPIC_API_KEY=your_key_here - ``` - -### Development Workflow - -```bash -# Check for errors in BAML files -baml check - -# Generate client code (Python and OpenAPI) -baml generate - -# Run BAML tests -baml test - -# Start development server with hot reload -baml dev - -# Interactive REPL for testing functions -baml repl -``` - -### Testing Functions - -BAML includes built-in tests for each function. Run them with: - -```bash -# Run all tests -baml test - -# Run specific test -baml test categorize_urgent_system_failure - -# Run tests for a specific file -baml test --file notifications.baml -``` - -## Available Functions - -### Notifications (`notifications.baml`) - -**CategorizeNotification** - -- Automatically categorize notifications by content -- Determines priority and lifetime -- Input: title, message, source context -- Output: category, priority, suggested lifetime, reasoning - -**SummarizeNotifications** - -- Generate daily/weekly notification digests -- Input: list of notifications, time period -- Output: summary, key actions, trends, recommendations - -**GroupRelatedNotifications** - -- Suggest intelligent grouping of related notifications -- Input: map of notification IDs to content -- Output: suggested groups with reasoning - -**IsNotificationStillRelevant** - -- Determine if old notifications should be archived -- Input: notification details, age, current system state -- Output: boolean indicating relevance - -### Questions (`questions.baml`) - -**GenerateSystemQuestion** - -- Create context-aware questions for ask service -- Input: check type, system state, user context, history -- Output: question text, suggested responses, follow-up actions - -**AnalyzeAnswer** - -- Interpret user's answer to extract intent -- Input: original question, user answer, expected response type -- Output: interpretation, confidence, suggested action - -**GenerateFollowUpQuestion** - -- Create clarifying follow-up questions -- Input: original question/answer, ambiguity reason -- Output: follow-up question with updated context - -**EvaluateQuestionEffectiveness** - -- Assess question quality for continuous improvement -- Input: question, response time, answer, outcome -- Output: effectiveness feedback and improvements - -**PersonalizeQuestion** - -- Adapt questions to user's communication style -- Input: base question, user preferences, history -- Output: personalized question - -### Monitoring (`monitoring.baml`) - -**AnalyzeLogs** - -- Comprehensive log analysis for errors and issues -- Input: service name, log entries, time window -- Output: error summary, severity, root cause, actions - -**DetectLogPatterns** - -- Identify recurring patterns in logs -- Input: log entries, time window -- Output: detected patterns with occurrence counts - -**AssessServiceHealth** - -- Overall health assessment of a service -- Input: service name, logs, metrics, expected behavior -- Output: health status, issues, recommendations - -**DetectPerformanceAnomaly** - -- Find metrics deviating from normal patterns -- Input: metric name, current/historical values, baseline -- Output: anomaly details and investigation steps (or null if normal) - -**CorrelateServiceErrors** - -- Find related failures across services -- Input: map of service errors, time window -- Output: correlation analysis and root cause hypothesis - -### CLI (`cli.baml`) - -**ParseNaturalLanguageCommand** - -- Convert natural language to CLI commands -- Input: user input, current context -- Output: action, parameters, confidence, confirmation needs - -**SuggestCommandCorrection** - -- Suggest corrections for failed commands -- Input: user input, error message -- Output: corrected command, explanation, alternatives - -**ProvideNaturalLanguageHelp** - -- Answer questions about CLI usage -- Input: user question, available commands -- Output: answer, examples, related topics - -**ExplainCommand** - -- Explain what a command will do before execution -- Input: command string, current context -- Output: clear explanation of effects and reversibility - -**SuggestCommandAliases** - -- Suggest helpful aliases based on usage patterns -- Input: command history, frequency map -- Output: map of suggested aliases to full commands - -### Hooks (`hooks.baml`) - -**AnalyzeShellEvent** - -- Decide if shell command needs notification -- Input: command, exit code, output, duration, context -- Output: notification decision with title, message, priority - -**LearnCommandPatterns** - -- Learn patterns from command history -- Input: command history, notification history -- Output: learned patterns with notification rules - -**AnalyzeCommandIntent** - -- Understand command before execution -- Input: command, execution context -- Output: purpose, long-running prediction, expected outcomes - -**GenerateCompletionNotification** - -- Create notification content for completed commands -- Input: command, exit code, duration, output, attempts -- Output: notification title and message - -**FilterRelevantOutput** - -- Extract important lines from verbose output -- Input: full output, exit code, max lines -- Output: filtered relevant output - -## Client Configuration - -### Primary Clients - -**LocalOllama** (Default) - -- Model: llama3.2 (configurable) -- URL: http://localhost:11434/v1 -- No API key needed -- Privacy: All processing local - -**LocalOllamaFast** - -- Model: qwen2.5:3b (smaller, faster) -- For low-latency operations - -**AgentdPrimary** (Recommended) - -- Fallback strategy: LocalOllama → CustomHaiku (Claude) -- Automatic failover if Ollama unavailable - -**AgentdFast** - -- Fallback strategy: LocalOllamaFast → CustomHaiku -- For time-sensitive operations - -### Switching Models - -Edit `clients.baml` to change models: - -```baml -client<llm> LocalOllama { - provider openai-generic - options { - base_url "http://localhost:11434/v1" - model "llama3.2" // Change this to: llama4, qwen2.5, mistral, etc. - default_role "user" - } -} -``` - -Available Ollama models: - -- `llama3.2` - Good balance of speed and quality -- `qwen2.5` - Fast and efficient -- `mistral` - Strong reasoning capabilities -- See: https://ollama.ai/library - -### Cloud Fallback - -To enable cloud fallback (optional): - -```bash -# For Anthropic Claude -export ANTHROPIC_API_KEY=your_key_here - -# Functions will use LocalOllama first, Claude if Ollama fails -``` - -## Integration with Rust Services - -### Rust Client Crate - -BAML integration uses a dedicated Rust crate (`crates/baml`) that wraps the REST API: - -```rust -use baml::{BamlClient, BamlClientConfig}; - -// Create client -let client = BamlClient::default(); - -// Call BAML function -let result = client.categorize_notification( - "Database Error", - "Connection failed", - "production" -).await?; -``` - -See `crates/baml/README.md` for complete API documentation. - -### Architecture - -``` -┌─────────────────┐ -│ Rust Services │ -│ (agentd-*) │ -└────────┬────────┘ - │ uses - ▼ -┌─────────────────┐ HTTP/REST ┌─────────────┐ -│ baml crate ├──────────────────────► BAML Server │ -│ (Rust client) │ │ (baml serve)│ -└─────────────────┘ └──────┬──────┘ - │ loads - ▼ - ┌─────────────┐ - │ baml_src/ │ - │ (*.baml) │ - └─────────────┘ - │ calls - ▼ - ┌─────────────┐ - │ Ollama │ - │ (local LLM)│ - └─────────────┘ -``` - -### Setup - -1. **Start BAML Server** (one-time, keep running): - - ```bash - baml serve - ``` - -2. **Use in Rust** (from any service): - - ```rust - use baml::BamlClient; - - let baml = BamlClient::default(); - let result = baml.categorize_notification(...).await?; - ``` - -## Usage Examples - -### Example 1: Categorize a Notification - -**Rust:** - -```rust -use baml::BamlClient; - -let client = BamlClient::default(); - -let result = client.categorize_notification( - "Database Connection Lost", - "Unable to connect to PostgreSQL", - "production monitoring" -).await?; - -println!("Category: {}", result.category); -println!("Priority: {}", result.priority); -println!("Reasoning: {}", result.reasoning); -``` - -**Expected Output:** - -``` -Category: urgent -Priority: urgent -Lifetime: persistent -Reasoning: Database connection failure affects all services and requires immediate attention... -``` - -### Example 2: Generate Smart Question - -**Rust:** - -```rust -let result = client.generate_system_question( - "tmux_sessions", - "0 tmux sessions running", - "User is in terminal, last session ended 2 hours ago", - "User typically runs 2-3 tmux sessions. Previous: 'dev-main'" -).await?; - -println!("Question: {}", result.question_text); -println!("Suggestions: {:?}", result.suggested_responses); -``` - -**Expected Output:** - -``` -Question: "No tmux sessions are running. Would you like to start your usual development environment?" -Suggestions: ["Yes, start dev-main", "No, not right now", "Start a new session"] -Urgency: normal -``` - -### Example 3: Analyze Logs - -**Rust:** - -```rust -let logs = vec![ - "[ERROR] Failed to connect to database".to_string(), - "[ERROR] Retry attempt 1 failed".to_string(), - "[WARN] Falling back to in-memory storage".to_string(), -]; - -let result = client.analyze_logs( - "agentd-notify", - &logs, - "last 5 minutes" -).await?; - -println!("Has Errors: {}", result.has_errors); -println!("Severity: {}", result.severity); -println!("Summary: {}", result.error_summary); -``` - -See `crates/baml/examples/` for complete working examples: - -```bash -# Start BAML server first -baml serve - -# Then run example -cargo run --example categorize_notification -``` - -## Performance Considerations - -### Latency - -- **Local Ollama**: 100-500ms per request - - - llama3.2: ~200-300ms - - qwen2.5:3b: ~100-150ms (faster model) - -- **Cloud Fallback**: 200-800ms per request - - Claude Haiku: ~200-400ms - - Higher latency but guaranteed availability - -### Token Usage (Cloud Models) - -Estimated tokens per function: - -- CategorizeNotification: ~200 tokens -- GenerateSystemQuestion: ~300 tokens -- AnalyzeLogs: ~500-1000 tokens (depends on log count) -- SummarizeNotifications: ~800-1500 tokens - -**Cost estimates** (Claude Haiku): - -- ~$0.25 per 1M input tokens -- Typical usage: <1000 calls/day = <$0.25/month -- Local Ollama is free - -### Caching - -For repeated similar requests, consider implementing response caching: - -```python -# Example: Cache notification categories -from functools import lru_cache - -@lru_cache(maxsize=100) -async def cached_categorize(title: str, message: str): - return await b.CategorizeNotification(title, message, "cache") -``` - -## Troubleshooting - -### BAML Check Fails - -```bash -baml check --display-all-warnings -``` - -Common issues: - -- Syntax errors in prompts (check quote escaping) -- Invalid field types in classes -- Undefined client references - -### Generation Fails - -```bash -baml generate --no-version-check -``` - -Issues: - -- Version mismatch: Update BAML CLI or `generators.baml` version -- Output directory permissions - -### Ollama Not Responding - -```bash -# Check if Ollama is running -curl http://localhost:11434/api/tags - -# Start Ollama -ollama serve - -# Pull model if not present -ollama pull llama3.2 -``` - -### Function Returns Poor Results - -1. **Check prompt quality**: Review function prompts in `.baml` files -2. **Try different model**: Edit `clients.baml` to use stronger model -3. **Add more context**: Provide more detailed input parameters -4. **Review test outputs**: Run `baml test` to see example outputs - -### Performance Issues - -1. **Use faster model**: Switch to qwen2.5:3b for speed -2. **Reduce prompt size**: Limit input array sizes -3. **Parallel requests**: BAML supports concurrent function calls -4. **Implement caching**: Cache results for repeated requests - -## Development Best Practices - -### Adding New Functions - -1. Define data models (classes) first -2. Write function signature with clear parameter types -3. Craft detailed prompt with examples and guidelines -4. Write at least 2 test cases (success + edge case) -5. Run `baml check` and fix any warnings -6. Test with `baml test` -7. Generate clients with `baml generate` - -### Prompt Engineering Tips - -1. **Be specific**: Clear instructions yield better results -2. **Provide examples**: Show desired output format -3. **Set constraints**: Define valid values, ranges, formats -4. **Handle edge cases**: Address ambiguous or missing inputs -5. **Use context variables**: `{{ ctx.output_format }}` ensures correct structure -6. **Test iteratively**: Use `baml repl` for rapid iteration - -### Testing Strategy - -1. **Unit tests**: BAML built-in tests for each function -2. **Integration tests**: Test with real service data -3. **Edge cases**: Test with empty inputs, errors, edge values -4. **Performance tests**: Measure latency for different models - -## Next Steps - -### Phase 1: Current Status ✓ - -- [x] BAML project initialized -- [x] Clients configured (Ollama + Claude fallback) -- [x] Functions defined for all use cases -- [x] Python client generated -- [x] Documentation created - -### Phase 2: Integration (Next) - -- [ ] Start BAML server (`baml serve`) -- [ ] Create Rust client wrapper in `crates/ollama` -- [ ] Integrate with agentd-notify service -- [ ] Test notification categorization -- [ ] Benchmark performance - -### Phase 3: Rollout - -- [ ] Integrate with agentd-ask for smart questions -- [ ] Add log analysis to agentd-monitor -- [ ] Enable natural language CLI parsing -- [ ] Implement hook intelligence -- [ ] Collect user feedback - -### Phase 4: Optimization - -- [ ] Fine-tune prompts based on real usage -- [ ] Implement response caching -- [ ] Add monitoring and metrics -- [ ] Optimize for latency -- [ ] Document production deployment - -## Resources - -### Documentation - -- BAML Docs: https://docs.boundaryml.com/home -- GitHub: https://github.com/BoundaryML/baml -- VSCode Extension: Search "BAML" in extensions - -### Related Files - -- Research doc: `../docs/research/baml-investigation.md` -- Main README: `../README.md` -- Ollama crate: `../crates/ollama/` - -### Support - -- GitHub Issues: https://github.com/BoundaryML/baml/issues -- Internal: See `docs/research/baml-investigation.md` - ---- - -**Last Updated:** 2025-11-08 -**BAML Version:** 0.213.0 -**Status:** Phase 1 Complete, Ready for Integration diff --git a/baml_src/cli.baml b/baml_src/cli.baml deleted file mode 100644 index a84af690..00000000 --- a/baml_src/cli.baml +++ /dev/null @@ -1,441 +0,0 @@ -// CLI natural language processing BAML functions for agentd -// -// This module enables natural language interaction with the agent CLI, -// allowing users to express commands in conversational language instead -// of remembering exact command syntax. - -// ============================================================================ -// Data Models -// ============================================================================ - -/// Parsed command intent from natural language input -class CommandIntent { - /// The identified action: "create_notification", "list_notifications", - /// "get_notification", "delete_notification", "respond_to_notification", - /// "trigger_ask_check", "answer_question", "get_service_status" - action string - - /// Extracted parameters for the command (e.g., {"title": "Meeting", "priority": "high"}) - parameters map<string, string> - - /// Confidence in the intent parsing (0.0 - 1.0) - confidence float - - /// Whether the command should ask for confirmation before executing - requires_confirmation bool - - /// Human-readable explanation of what will be executed - execution_summary string - - /// Warning messages if the command might have unintended consequences - warnings string[]? -} - -/// Suggestion for command completion or correction -class CommandSuggestion { - /// Suggested command to run - suggested_command string - - /// Explanation of what this command does - explanation string - - /// Confidence that this is what the user intended - confidence float - - /// Alternative suggestions - alternatives string[]? -} - -/// Help information for natural language query -class HelpResponse { - /// Direct answer to the user's question - answer string - - /// Relevant examples showing how to accomplish the task - examples string[] - - /// Related commands or topics - related_topics string[] - - /// Whether this requires additional context from user - needs_more_info bool - - /// Follow-up questions to clarify user's intent - followup_questions string[]? -} - -// ============================================================================ -// Functions -// ============================================================================ - -/// Parse natural language input into a structured command -/// -/// Interprets user's natural language input and maps it to specific -/// agent CLI commands with appropriate parameters. -/// -/// # Arguments -/// - user_input: The natural language command from the user -/// - current_context: Current working context (e.g., current directory, recent commands) -/// -/// # Returns -/// CommandIntent with parsed action, parameters, and confidence -function ParseNaturalLanguageCommand( - user_input: string, - current_context: string -) -> CommandIntent { - client AgentdFast - prompt #" - Parse this natural language command for the agent CLI: - - **User Input:** "{{ user_input }}" - - **Current Context:** {{ current_context }} - - **Available Actions:** - - 1. **create_notification**: Create a new notification - Parameters: title, message, priority (low/normal/high/urgent), lifetime (ephemeral/persistent) - Examples: "remind me to deploy", "create urgent notification about database" - - 2. **list_notifications**: List notifications - Parameters: filter (all/actionable/urgent), status (pending/responded/dismissed) - Examples: "show me urgent notifications", "list all pending notifications" - - 3. **get_notification**: Get details of a specific notification - Parameters: notification_id or description - Examples: "show notification about deployment", "get details of last notification" - - 4. **delete_notification**: Delete a notification - Parameters: notification_id or description - Examples: "delete the database notification", "remove all old notifications" - - 5. **respond_to_notification**: Respond to a notification that requires response - Parameters: notification_id, response_text - Examples: "answer the deployment question with yes", "respond to last notification: approved" - - 6. **trigger_ask_check**: Trigger a system check - Parameters: check_type (optional, e.g., "tmux_sessions") - Examples: "check tmux", "run system checks" - - 7. **answer_question**: Answer a pending question - Parameters: question_id or description, answer - Examples: "answer the tmux question with yes", "respond to system question: no" - - 8. **get_service_status**: Check status of services - Parameters: service_name (optional) - Examples: "are services running", "check notify service status" - - **Parsing Guidelines:** - - 1. **Extract Intent**: What is the user trying to do? - 2. **Extract Parameters**: Pull out relevant values (titles, priorities, filters, etc.) - 3. **Handle Ambiguity**: - - If unclear, set confidence lower and requires_confirmation true - - Provide clear execution summary - 4. **Safety Checks**: - - Deletions should require confirmation - - Batch operations should warn user - 5. **Context Usage**: - - "last notification", "recent alert" → use context to resolve - - Relative references → interpret based on current state - - **Confidence Levels:** - - High (0.9-1.0): Clear, unambiguous command - - Medium (0.6-0.9): Reasonable interpretation, slight ambiguity - - Low (0.0-0.6): Multiple interpretations possible, needs confirmation - - Parse the command now. - - {{ ctx.output_format }} - "# -} - -/// Suggest command corrections or completions -/// -/// When user input is unclear or potentially incorrect, suggest -/// what they might have meant to type. -/// -/// # Arguments -/// - user_input: The user's attempted command (possibly malformed) -/// - error_message: Error message from attempted execution (if any) -/// -/// # Returns -/// CommandSuggestion with corrected command and explanation -function SuggestCommandCorrection( - user_input: string, - error_message: string -) -> CommandSuggestion { - client AgentdFast - prompt #" - The user tried this command which failed: - - **Input:** "{{ user_input }}" - - **Error:** {{ error_message }} - - **Your Task:** Suggest what the user likely meant to do. - - **Common Issues:** - - 1. **Typos**: "notif" → "notification", "lst" → "list" - 2. **Wrong Action**: "show notifications" when they meant "list notifications" - 3. **Missing Parameters**: "create notification" without title/message - 4. **Invalid Parameter Values**: priority "super high" → "urgent" - 5. **Wrong Command Format**: Using flags that don't exist - - **Suggestion Guidelines:** - - - Suggest the corrected command - - Explain what it does - - Provide 1-2 alternatives if multiple interpretations exist - - High confidence if fix is obvious, lower if uncertain - - Provide suggestion now. - - {{ ctx.output_format }} - "# -} - -/// Provide help for natural language queries -/// -/// Answers user questions about how to use the CLI or accomplish -/// specific tasks. -/// -/// # Arguments -/// - user_question: The user's question -/// - available_commands: List of available commands with descriptions -/// -/// # Returns -/// HelpResponse with answer, examples, and related topics -function ProvideNaturalLanguageHelp( - user_question: string, - available_commands: string -) -> HelpResponse { - client AgentdPrimary - prompt #" - Answer this user's question about the agent CLI: - - **Question:** "{{ user_question }}" - - **Available Commands:** - {{ available_commands }} - - **Help Guidelines:** - - 1. **Direct Answer**: Concisely answer the question - 2. **Examples**: Provide 2-3 practical examples - 3. **Related Topics**: Suggest related commands or features - 4. **Clarification**: If question is vague, ask follow-up questions - - **Types of Questions:** - - - **How do I...?** → Provide command examples - - **What is...?** → Explain concept/feature - - **Can I...?** → Yes/no + how to do it if possible - - **Why...?** → Explain reasoning or design decision - - **Example Response Style:** - - Question: "How do I create an urgent notification?" - Answer: "You can create an urgent notification using the `create` command with the `--priority urgent` flag..." - Examples: - - `agent notify create --title "Critical" --message "Server down" --priority urgent` - - `agent "create urgent notification about database failure"` - Related: notification priorities, notification lifetimes - - Provide helpful response now. - - {{ ctx.output_format }} - "# -} - -/// Explain what a command will do before execution -/// -/// Generates a human-readable explanation of a command's effects, -/// useful for confirmation prompts or educational purposes. -/// -/// # Arguments -/// - command_string: The command to explain -/// - current_context: Current system state for context-specific explanations -/// -/// # Returns -/// String with clear explanation of command effects -function ExplainCommand( - command_string: string, - current_context: string -) -> string { - client AgentdFast - prompt #" - Explain what this command will do: - - **Command:** `{{ command_string }}` - - **Current Context:** {{ current_context }} - - **Explanation Guidelines:** - - 1. **What**: What action will be performed? - 2. **Where**: What objects/resources will be affected? - 3. **Impact**: What will change in the system? - 4. **Reversibility**: Can this be undone? If so, how? - - **Example Explanations:** - - Command: `agent notify delete abc123` - Explanation: "This will permanently delete the notification with ID abc123 titled 'Database backup completed'. This action cannot be undone." - - Command: `agent ask trigger` - Explanation: "This will run all registered system checks and create notifications for any questions that need answering. Currently enabled checks: tmux sessions." - - Provide clear, concise explanation in 1-2 sentences. - - {{ ctx.output_format }} - "# -} - -/// Generate command aliases or shortcuts -/// -/// Suggests memorable aliases for frequently used commands based -/// on user's command history and patterns. -/// -/// # Arguments -/// - command_history: List of recent commands the user has run -/// - frequency_map: Map of commands to how often they're used -/// -/// # Returns -/// Map of suggested aliases to their full commands with explanations -function SuggestCommandAliases( - command_history: string[], - frequency_map: map<string, int> -) -> map<string, string> { - client AgentdFast - prompt #" - Analyze this command history and suggest useful aliases: - - **Recent Commands:** - {% for cmd in command_history %} - - {{ cmd }} - {% endfor %} - - **Frequency Data:** - {{ frequency_map }} - - **Alias Suggestion Guidelines:** - - 1. **Frequency**: Prioritize frequently used commands - 2. **Length**: Longer commands benefit more from aliases - 3. **Memorability**: Aliases should be intuitive and short - 4. **Patterns**: Look for common parameter combinations - - **Example Aliases:** - - - `agent notify list --actionable` → `agent pending` or `agent todo` - - `agent notify create --priority urgent` → `agent urgent-notify` - - `agent ask trigger` → `agent check` - - Return a map of alias → full command with explanation. - Format: "alias-name": "full command | Explanation: why this is useful" - - Maximum 5 suggestions, only for commands used 3+ times. - - {{ ctx.output_format }} - "# -} - -// ============================================================================ -// Tests -// ============================================================================ - -test parse_simple_create_notification { - functions [ParseNaturalLanguageCommand] - args { - user_input "remind me to deploy the application tomorrow" - current_context "Current time: 2024-01-15 14:00, working directory: /projects/myapp" - } -} - -test parse_list_notifications { - functions [ParseNaturalLanguageCommand] - args { - user_input "show me all urgent notifications" - current_context "User has 15 total notifications, 3 are urgent" - } -} - -test parse_ambiguous_delete { - functions [ParseNaturalLanguageCommand] - args { - user_input "delete old notifications" - current_context "User has 10 notifications from last week, 5 from yesterday" - } -} - -test suggest_correction_typo { - functions [SuggestCommandCorrection] - args { - user_input "notif list" - error_message "Unknown command 'notif'. Did you mean 'notify'?" - } -} - -test suggest_correction_missing_param { - functions [SuggestCommandCorrection] - args { - user_input "agent notify create" - error_message "Missing required argument '--title'" - } -} - -test help_how_to_create { - functions [ProvideNaturalLanguageHelp] - args { - user_question "How do I create a high priority notification that persists?" - available_commands "notify create [--title] [--message] [--priority low|normal|high|urgent] [--lifetime ephemeral|persistent]" - } -} - -test help_what_is { - functions [ProvideNaturalLanguageHelp] - args { - user_question "What is an ephemeral notification?" - available_commands "notify create [--lifetime ephemeral|persistent] - ephemeral notifications auto-dismiss after a timeout" - } -} - -test explain_delete_command { - functions [ExplainCommand] - args { - command_string "agent notify delete --all" - current_context "User has 25 notifications (3 urgent, 12 normal, 10 low)" - } -} - -test explain_create_command { - functions [ExplainCommand] - args { - command_string "agent notify create --title 'Deploy' --priority urgent --lifetime persistent" - current_context "User currently has 5 notifications, none about deployment" - } -} - -test suggest_aliases { - functions [SuggestCommandAliases] - args { - command_history [ - "agent notify list --actionable", - "agent notify list --actionable", - "agent notify list --actionable", - "agent notify list --actionable", - "agent notify create --priority urgent --lifetime persistent", - "agent notify create --priority urgent --lifetime persistent", - "agent notify create --priority urgent --lifetime persistent", - "agent ask trigger", - "agent ask trigger" - ] - frequency_map { - "notify list --actionable": 4, - "notify create --priority urgent --lifetime persistent": 3, - "ask trigger": 2 - } - } -} diff --git a/baml_src/clients.baml b/baml_src/clients.baml deleted file mode 100644 index 32fb8c47..00000000 --- a/baml_src/clients.baml +++ /dev/null @@ -1,171 +0,0 @@ -// Learn more about clients at https://docs.boundaryml.com/docs/snippets/clients/overview - -// Using the new OpenAI Responses API for enhanced formatting -client<llm> CustomGPT5 { - provider openai-responses - options { - model "gpt-5" - api_key env.OPENAI_API_KEY - } -} - -client<llm> CustomGPT5Mini { - provider openai-responses - retry_policy Exponential - options { - model "gpt-5-mini" - api_key env.OPENAI_API_KEY - } -} - -// Openai with chat completion -client<llm> CustomGPT5Chat { - provider openai - options { - model "gpt-5" - api_key env.OPENAI_API_KEY - } -} - -// Latest Anthropic Claude 4 models -client<llm> CustomOpus4 { - provider anthropic - options { - model "claude-opus-4-1-20250805" - api_key env.ANTHROPIC_API_KEY - } -} - -client<llm> CustomSonnet4 { - provider anthropic - options { - model "claude-sonnet-4-20250514" - api_key env.ANTHROPIC_API_KEY - } -} - -client<llm> CustomHaiku { - provider anthropic - retry_policy Constant - options { - model "claude-3-5-haiku-20241022" - api_key env.ANTHROPIC_API_KEY - } -} - -// Example Google AI client (uncomment to use) -// client<llm> CustomGemini { -// provider google-ai -// options { -// model "gemini-2.5-pro" -// api_key env.GOOGLE_API_KEY -// } -// } - -// Example AWS Bedrock client (uncomment to use) -// client<llm> CustomBedrock { -// provider aws-bedrock -// options { -// model "anthropic.claude-sonnet-4-20250514-v1:0" -// region "us-east-1" -// // AWS credentials are auto-detected from env vars -// } -// } - -// Example Azure OpenAI client (uncomment to use) -// client<llm> CustomAzure { -// provider azure-openai -// options { -// model "gpt-5" -// api_key env.AZURE_OPENAI_API_KEY -// base_url "https://MY_RESOURCE_NAME.openai.azure.com/openai/deployments/MY_DEPLOYMENT_ID" -// api_version "2024-10-01-preview" -// } -// } - -// Example Vertex AI client (uncomment to use) -// client<llm> CustomVertex { -// provider vertex-ai -// options { -// model "gemini-2.5-pro" -// location "us-central1" -// // Uses Google Cloud Application Default Credentials -// } -// } - -// Ollama client for local models - Primary client for agentd -client<llm> LocalOllama { - provider openai-generic - retry_policy Constant - options { - base_url "http://localhost:11434/v1" - model "gpt-oss:120b-cloud" // Can be changed to llama4, qwen, etc. - default_role "user" - // No API key needed for local Ollama - } -} - -// Fast local model for quick operations -client<llm> LocalOllamaFast { - provider openai-generic - retry_policy Constant - options { - base_url "http://localhost:11434/v1" - model "qwen2.5:3b" // Smaller, faster model - default_role "user" - } -} - -// Primary client with fallback strategy for agentd -// Uses local Ollama first, falls back to Claude Haiku if Ollama unavailable -client<llm> AgentdPrimary { - provider fallback - options { - strategy [LocalOllama, CustomHaiku] - } -} - -// Fast client for low-latency operations -client<llm> AgentdFast { - provider fallback - options { - strategy [LocalOllamaFast, CustomHaiku] - } -} - -// https://docs.boundaryml.com/docs/snippets/clients/round-robin -client<llm> CustomFast { - provider round-robin - options { - // This will alternate between the two clients - strategy [CustomGPT5Mini, CustomHaiku] - } -} - -// https://docs.boundaryml.com/docs/snippets/clients/fallback -client<llm> OpenaiFallback { - provider fallback - options { - // This will try the clients in order until one succeeds - strategy [CustomGPT5Mini, CustomGPT5] - } -} - -// https://docs.boundaryml.com/docs/snippets/clients/retry -retry_policy Constant { - max_retries 3 - strategy { - type constant_delay - delay_ms 200 - } -} - -retry_policy Exponential { - max_retries 2 - strategy { - type exponential_backoff - delay_ms 300 - multiplier 1.5 - max_delay_ms 10000 - } -} diff --git a/baml_src/generators.baml b/baml_src/generators.baml deleted file mode 100644 index b0287567..00000000 --- a/baml_src/generators.baml +++ /dev/null @@ -1,12 +0,0 @@ -// This helps use auto generate libraries you can use in the language of -// your choice. You can have multiple generators if you use multiple languages. -// Just ensure that the output_dir is different for each generator. - -// OpenAPI/REST generator for Rust integration -// Rust services will call the BAML server via REST API (baml serve) -generator openapi_spec { - output_type "rest/openapi" - output_dir "../baml_openapi" - version "0.213.0" - default_client_mode async -} diff --git a/baml_src/hooks.baml b/baml_src/hooks.baml deleted file mode 100644 index bcd7a599..00000000 --- a/baml_src/hooks.baml +++ /dev/null @@ -1,528 +0,0 @@ -// Hook analysis BAML functions for agentd -// -// This module provides intelligent analysis of shell hook events, -// determining which events warrant notifications and how to present them. - -// ============================================================================ -// Data Models -// ============================================================================ - -/// Decision about whether and how to notify for a hook event -class HookAction { - /// Whether a notification should be sent - should_notify bool - - /// Notification title (if should_notify is true) - notification_title string - - /// Notification message body (if should_notify is true) - notification_message string - - /// Suggested priority: "low", "normal", "high", "urgent" - priority string - - /// Brief explanation of the decision - reasoning string - - /// Metadata to attach to the notification for context - metadata map<string, string> - - /// Whether this event indicates a problem - indicates_problem bool - - /// Suggested actions for the user (if applicable) - suggested_actions string[]? -} - -/// Pattern learned from hook event history -class HookPattern { - /// Description of the pattern - pattern_description string - - /// Commands or events that match this pattern - matching_criteria string - - /// Whether events matching this pattern should notify - should_notify bool - - /// Suggested notification priority for this pattern - notification_priority string - - /// How many times this pattern has been observed - occurrence_count int - - /// Confidence in this pattern (0.0 - 1.0) - confidence float -} - -/// Intelligence about command execution -class CommandInsight { - /// What the command appears to be doing - purpose string - - /// Whether this is a long-running command - is_long_running bool - - /// Expected behavior (success indicators) - expected_outcomes string[] - - /// Potential issues to watch for - potential_issues string[] - - /// Whether user typically cares about this command's completion - user_cares_about_completion bool -} - -// ============================================================================ -// Functions -// ============================================================================ - -/// Analyze a shell event to determine if notification is needed -/// -/// Intelligently decides whether a shell command execution warrants -/// a notification, and if so, what that notification should say. -/// -/// # Arguments -/// - command: The shell command that was executed -/// - exit_code: Exit code of the command (0 = success) -/// - output: stdout/stderr from the command (truncated if long) -/// - duration_ms: How long the command took to execute -/// - context: Additional context (current directory, time of day, etc.) -/// -/// # Returns -/// HookAction with notification decision and details -function AnalyzeShellEvent( - command: string, - exit_code: int, - output: string, - duration_ms: int, - context: string -) -> HookAction { - client AgentdFast - prompt #" - Analyze this shell command execution: - - **Command:** `{{ command }}` - - **Exit Code:** {{ exit_code }} (0 = success, non-zero = error) - - **Duration:** {{ duration_ms }}ms - - **Output:** - ``` - {{ output }} - ``` - - **Context:** {{ context }} - - **Notification Decision Guidelines:** - - **DO Notify For:** - 1. **Long-running commands completing** (> 30 seconds) - - Build commands (make, cargo build, npm install) - - Tests (cargo test, npm test, pytest) - - Deployments - - Large file operations - - 2. **Commands with errors** (exit_code != 0) - - Especially if non-interactive - - Include error message in notification - - 3. **User-defined patterns** - - Commands explicitly marked for notification - - Commands matching learned patterns - - 4. **Background/async operations** - - Downloads, uploads - - Background jobs - - **DON'T Notify For:** - 1. **Fast interactive commands** (< 5 seconds) - - ls, cd, pwd, etc. - - Quick file edits - - Status checks - - 2. **Expected failures** - - Test commands often expected to fail during development - - Conditional commands (grep with no match) - - 3. **Silent commands** - - Commands that explicitly suppress output - - Background maintenance tasks - - **Notification Priority:** - - **urgent**: Critical failures affecting system/data - - **high**: Command failures, unexpected errors - - **normal**: Long-running commands completed successfully - - **low**: Informational, optional completions - - **Notification Content:** - - Title: Brief, actionable (e.g., "Build Failed" not "Command Completed") - - Message: Include key information (exit code, timing, error preview) - - Context: Directory, relevant output lines - - Make your decision now. - - {{ ctx.output_format }} - "# -} - -/// Learn patterns from command history -/// -/// Analyzes historical command executions to identify patterns -/// and improve future notification decisions. -/// -/// # Arguments -/// - command_history: Recent command executions with outcomes -/// - notification_history: Which commands generated notifications and user response -/// -/// # Returns -/// Array of HookPattern objects describing learned patterns -function LearnCommandPatterns( - command_history: string[], - notification_history: string[] -) -> HookPattern[] { - client AgentdPrimary - prompt #" - Learn patterns from command execution history: - - **Command History:** - {% for cmd in command_history %} - - {{ cmd }} - {% endfor %} - - **Notification History (and user reactions):** - {% for notif in notification_history %} - - {{ notif }} - {% endfor %} - - **Pattern Learning Guidelines:** - - 1. **Frequency Patterns**: - - Commands run regularly (daily backups, cron jobs) - - Only notify if they fail, not on success - - 2. **Duration Patterns**: - - If command usually takes X seconds, notify if it takes significantly longer - - Learn typical execution times for common commands - - 3. **Outcome Patterns**: - - Some commands regularly fail but that's expected (test-driven development) - - Other commands should always succeed (deployments, backups) - - 4. **User Response Patterns**: - - If user dismisses notifications for certain commands without action - - If user always acts on certain notification types - - 5. **Context Patterns**: - - Time-of-day patterns (late-night commands might be more important) - - Directory patterns (production dirs vs test dirs) - - **Pattern Output:** - - Describe each pattern clearly - - Provide matching criteria (regex, command prefix, etc.) - - Specify notification behavior - - Include confidence level - - Return only significant patterns (occurrence_count >= 3, confidence >= 0.7). - - {{ ctx.output_format }} - "# -} - -/// Provide insights about a command before execution -/// -/// Analyzes a command that's about to run and provides intelligence -/// about what to expect, useful for setting up appropriate monitoring. -/// -/// # Arguments -/// - command: The command that will be executed -/// - execution_context: Where and when it's running -/// -/// # Returns -/// CommandInsight with purpose, expectations, and monitoring suggestions -function AnalyzeCommandIntent( - command: string, - execution_context: string -) -> CommandInsight { - client AgentdFast - prompt #" - Analyze this command that's about to execute: - - **Command:** `{{ command }}` - - **Context:** {{ execution_context }} - - **Analysis Goals:** - - 1. **Purpose**: What is this command doing? - - Building/compiling - - Testing - - Deploying - - Data processing - - System maintenance - - File operations - - 2. **Long-running Detection**: - - Likely to take > 30 seconds? - - Based on command type (build, test, download) - - 3. **Expected Outcomes**: - - What indicates success? - - What outputs are normal? - - 4. **Potential Issues**: - - Common failure modes for this command - - What to watch for in output - - 5. **User Interest**: - - Do users typically care when this completes? - - Background task vs. active work - - **Examples:** - - - `cargo build --release` → Long-running, user cares about completion - - `git status` → Fast, informational only - - `pytest tests/` → Medium duration, user cares about results - - `scp large-file.zip server:/path` → Long-running, user wants notification - - Provide analysis now. - - {{ ctx.output_format }} - "# -} - -/// Generate smart notification content for command completion -/// -/// Creates informative, actionable notification content for a -/// completed command execution. -/// -/// # Arguments -/// - command: The command that completed -/// - exit_code: Exit code (0 = success) -/// - duration_ms: Execution time -/// - output_summary: Key lines from output -/// - previous_attempts: Whether this command was retried -/// -/// # Returns -/// Notification title and message as a JSON-formatted string -function GenerateCompletionNotification( - command: string, - exit_code: int, - duration_ms: int, - output_summary: string, - previous_attempts: int -) -> string { - client AgentdFast - prompt #" - Generate notification content for this completed command: - - **Command:** `{{ command }}` - - **Exit Code:** {{ exit_code }} - - **Duration:** {{ duration_ms }}ms ({{ duration_ms / 1000 }} seconds) - - **Output Summary:** - {{ output_summary }} - - **Previous Attempts:** {{ previous_attempts }} - - **Notification Guidelines:** - - **Title:** - - Action-oriented and clear - - Include key result (success/failure) - - Keep under 50 characters - - Examples: - - "Build Completed Successfully" (not "Command Finished") - - "Tests Failed: 3 errors" (not "Test Run Done") - - "Deployment Finished in 2m" (not "Deploy Command Completed") - - **Message:** - - Key information first - - Include timing if significant - - Include error preview if failed - - Include action items if failure - - Keep concise (2-3 lines max) - - **Examples:** - - Success: - Title: "Build Completed" - Message: "cargo build --release finished successfully in 2m 15s. Binary ready at target/release/myapp." - - Failure: - Title: "Build Failed" - Message: "Compilation error in src/main.rs:42. Check output for details. Retry attempt 1 of 3." - - Return JSON: {"title": "...", "message": "..."} - - {{ ctx.output_format }} - "# -} - -/// Filter out noise from command output -/// -/// Extracts the most important lines from verbose command output -/// for inclusion in notifications. -/// -/// # Arguments -/// - full_output: Complete command output -/// - exit_code: Whether command succeeded -/// - max_lines: Maximum lines to include in summary -/// -/// # Returns -/// String with filtered, most relevant output lines -function FilterRelevantOutput( - full_output: string, - exit_code: int, - max_lines: int -) -> string { - client AgentdFast - prompt #" - Extract the most relevant {{ max_lines }} lines from this command output: - - ``` - {{ full_output }} - ``` - - **Exit Code:** {{ exit_code }} - - **Filtering Guidelines:** - - **If Failed (exit_code != 0):** - - Prioritize error messages - - Include context around errors - - Include the last few lines (often contain error summary) - - **If Succeeded (exit_code == 0):** - - Prioritize summary lines - - Include final status/completion messages - - Skip verbose progress output - - **Always:** - - Skip timestamp-only lines - - Skip debug/trace level logs - - Skip blank lines - - Keep warning messages - - Keep lines with numbers/statistics (e.g., "Compiled 42 files") - - Return the {{ max_lines }} most important lines, preserving their original text. - Separate lines with newlines. If output is shorter than {{ max_lines }} lines, return all of it. - - {{ ctx.output_format }} - "# -} - -// ============================================================================ -// Tests -// ============================================================================ - -test analyze_long_build_success { - functions [AnalyzeShellEvent] - args { - command "cargo build --release" - exit_code 0 - output " Compiling myapp v0.1.0\n Finished release [optimized] target(s) in 2m 15s" - duration_ms 135000 - context "Directory: /projects/myapp, Time: 14:30" - } -} - -test analyze_quick_command { - functions [AnalyzeShellEvent] - args { - command "ls -la" - exit_code 0 - output "total 48\ndrwxr-xr-x 12 user staff 384 Jan 15 14:00 ." - duration_ms 15 - context "Directory: /projects, Time: 14:30" - } -} - -test analyze_failed_test { - functions [AnalyzeShellEvent] - args { - command "cargo test" - exit_code 101 - output "running 10 tests\ntest auth_test ... FAILED\ntest db_test ... ok\n\nfailures:\n auth_test\n\ntest result: FAILED. 9 passed; 1 failed" - duration_ms 5000 - context "Directory: /projects/myapp/tests, Time: 14:45" - } -} - -test learn_patterns_from_history { - functions [LearnCommandPatterns] - args { - command_history [ - "cargo test | exit_code: 0 | duration: 4500ms | no notification", - "cargo test | exit_code: 101 | duration: 4200ms | notified, user reviewed", - "cargo build --release | exit_code: 0 | duration: 120000ms | notified, user acknowledged", - "cargo build --release | exit_code: 0 | duration: 118000ms | notified, user acknowledged", - "ls -la | exit_code: 0 | duration: 10ms | no notification", - "git status | exit_code: 0 | duration: 50ms | no notification" - ] - notification_history [ - "cargo test FAILED → user immediately checked logs", - "cargo build completed (2min) → user acknowledged but no action", - "cargo build completed (2min) → user acknowledged but no action" - ] - } -} - -test analyze_command_intent_build { - functions [AnalyzeCommandIntent] - args { - command "npm run build" - execution_context "Project: web-app, Directory: /projects/webapp, User actively developing" - } -} - -test analyze_command_intent_deploy { - functions [AnalyzeCommandIntent] - args { - command "./deploy-production.sh" - execution_context "Directory: /projects/myapp/scripts, Time: 22:30 (late night)" - } -} - -test generate_success_notification { - functions [GenerateCompletionNotification] - args { - command "cargo test --all" - exit_code 0 - duration_ms 8500 - output_summary "running 42 tests\ntest result: ok. 42 passed; 0 failed; 0 ignored" - previous_attempts 0 - } -} - -test generate_failure_notification { - functions [GenerateCompletionNotification] - args { - command "cargo clippy --all-targets" - exit_code 1 - duration_ms 3200 - output_summary "error: this function has too many arguments (8/7)\n --> src/main.rs:42:1\nwarning: 5 warnings emitted\nerror: aborting due to previous error" - previous_attempts 0 - } -} - -test filter_error_output { - functions [FilterRelevantOutput] - args { - full_output "Compiling myapp v0.1.0\nCompiling dep1 v1.0.0\nCompiling dep2 v2.0.0\nerror[E0425]: cannot find value `foo` in this scope\n --> src/main.rs:42:5\n |\n42 | foo.bar()\n | ^^^ not found in this scope\n\nerror: aborting due to previous error\nFor more information about this error, try `rustc --explain E0425`" - exit_code 1 - max_lines 5 - } -} - -test filter_success_output { - functions [FilterRelevantOutput] - args { - full_output " Compiling dep1 v1.0.0\n Compiling dep2 v2.0.0\n Compiling dep3 v3.0.0\n Compiling myapp v0.1.0\n Finished release [optimized] target(s) in 2m 15s" - exit_code 0 - max_lines 3 - } -} diff --git a/baml_src/monitoring.baml b/baml_src/monitoring.baml deleted file mode 100644 index 0e617111..00000000 --- a/baml_src/monitoring.baml +++ /dev/null @@ -1,533 +0,0 @@ -// Monitoring and log analysis BAML functions for agentd -// -// This module provides AI-powered log analysis, error detection, -// and system health monitoring capabilities. - -// ============================================================================ -// Data Models -// ============================================================================ - -/// Analysis results for log entries -class LogAnalysis { - /// Whether errors were detected in the logs - has_errors bool - - /// High-level summary of error patterns or issues - error_summary string - - /// List of services or components affected by issues - affected_services string[] - - /// Specific, actionable steps to resolve issues - suggested_actions string[] - - /// Severity level: "info", "warning", "error", "critical" - severity string - - /// Root cause analysis (if determinable from logs) - root_cause string - - /// Whether immediate action is required - requires_immediate_attention bool - - /// Estimated impact on system operations - impact_assessment string -} - -/// Pattern detection in logs -class LogPattern { - /// Description of the detected pattern - pattern_description string - - /// How many times this pattern appeared - occurrence_count int - - /// Time range when pattern was observed - time_range string - - /// Whether this pattern indicates a problem - is_problematic bool - - /// Confidence in pattern detection (0.0 - 1.0) - confidence float - - /// Suggested investigation steps - investigation_steps string[] -} - -/// Health assessment of a service -class ServiceHealth { - /// Service name - service_name string - - /// Overall health status: "healthy", "degraded", "unhealthy", "critical" - status string - - /// Specific health indicators and their values - indicators map<string, string> - - /// Issues detected (if any) - issues string[] - - /// Recommended remediation actions - recommendations string[] - - /// Confidence in health assessment (0.0 - 1.0) - confidence float - - /// Predicted impact if issues not addressed - impact_if_unresolved string? -} - -/// Performance anomaly detection -class PerformanceAnomaly { - /// Metric that shows anomalous behavior - metric_name string - - /// Current value of the metric - current_value string - - /// Expected/normal value range - expected_range string - - /// Deviation from normal (percentage or description) - deviation string - - /// Possible causes of the anomaly - possible_causes string[] - - /// Recommended investigation steps - investigation_steps string[] - - /// Severity of the anomaly - severity string -} - -// ============================================================================ -// Functions -// ============================================================================ - -/// Analyze log entries for errors and issues -/// -/// Performs comprehensive analysis of log entries to detect errors, -/// identify patterns, and provide actionable recommendations. -/// -/// # Arguments -/// - service_name: Name of the service generating the logs -/// - log_entries: Array of log lines to analyze -/// - time_window: Time period these logs cover (e.g., "last 5 minutes", "2024-01-15 10:00-10:30") -/// -/// # Returns -/// LogAnalysis with error detection, severity assessment, and recommended actions -function AnalyzeLogs( - service_name: string, - log_entries: string[], - time_window: string -) -> LogAnalysis { - client AgentdPrimary - prompt #" - Analyze these logs from **{{ service_name }}** ({{ time_window }}): - - {% for entry in log_entries %} - {{ entry }} - {% endfor %} - - **Analysis Guidelines:** - - 1. **Error Detection**: - - Look for error messages, stack traces, exceptions - - Identify warning signs that might lead to errors - - Detect unusual patterns or anomalies - - 2. **Severity Assessment**: - - **critical**: Service down, data loss, security breach - - **error**: Functionality broken, but service running - - **warning**: Potential issues, degraded performance - - **info**: No issues detected, informational only - - 3. **Root Cause**: - - Analyze error messages and context - - Look for common causes (connection issues, resource exhaustion, config errors) - - Consider timing and sequence of events - - 4. **Affected Services**: - - Which components are directly impacted? - - What downstream services might be affected? - - 5. **Actionable Recommendations**: - - Be specific: "Restart service X" not "fix the problem" - - Prioritize actions by impact - - Include investigation steps for unclear issues - - 6. **Immediate Attention**: - - Set to true only if system is down or actively failing - - Consider if the issue can wait for normal business hours - - Provide comprehensive analysis now. - - {{ ctx.output_format }} - "# -} - -/// Detect patterns in log data -/// -/// Identifies recurring patterns, trends, or anomalies in log entries -/// that might indicate systemic issues or emerging problems. -/// -/// # Arguments -/// - log_entries: Array of log entries to analyze -/// - pattern_window: Time window for pattern detection (e.g., "1 hour", "24 hours") -/// -/// # Returns -/// Array of LogPattern objects describing detected patterns -function DetectLogPatterns( - log_entries: string[], - pattern_window: string -) -> LogPattern[] { - client AgentdPrimary - prompt #" - Analyze these logs to detect patterns over {{ pattern_window }}: - - {% for entry in log_entries %} - {{ entry }} - {% endfor %} - - **Pattern Detection Guidelines:** - - Look for: - 1. **Repetitive Errors**: - - Same error occurring multiple times - - Increasing frequency might indicate escalating problem - - 2. **Temporal Patterns**: - - Errors at specific times (e.g., every hour during scheduled job) - - Gradual degradation over time - - 3. **Correlated Events**: - - Multiple services showing errors around the same time - - Cascade failures (A fails → B fails → C fails) - - 4. **Resource Patterns**: - - Memory/CPU usage warnings - - Connection pool exhaustion - - Rate limiting triggers - - 5. **Anomalies**: - - Unusual log messages that don't fit normal patterns - - Sudden changes in log volume - - New types of errors - - For each pattern: - - Describe what you observed - - Count occurrences - - Assess if it's problematic - - Provide confidence level - - Suggest investigation steps - - Return a list of significant patterns. Don't report trivial patterns. - - {{ ctx.output_format }} - "# -} - -/// Assess overall health of a service -/// -/// Evaluates service health based on logs, metrics, and system state, -/// providing a comprehensive health assessment. -/// -/// # Arguments -/// - service_name: Name of the service to assess -/// - recent_logs: Recent log entries from the service -/// - metrics: Key metrics (JSON string with metric names and values) -/// - expected_behavior: Description of normal/healthy behavior -/// -/// # Returns -/// ServiceHealth with status, issues, and recommendations -function AssessServiceHealth( - service_name: string, - recent_logs: string[], - metrics: string, - expected_behavior: string -) -> ServiceHealth { - client AgentdPrimary - prompt #" - Assess the health of **{{ service_name }}**: - - **Recent Logs:** - {% for log in recent_logs %} - {{ log }} - {% endfor %} - - **Metrics:** - {{ metrics }} - - **Expected Healthy Behavior:** - {{ expected_behavior }} - - **Health Assessment Guidelines:** - - 1. **Status Determination**: - - **healthy**: Operating normally, no issues - - **degraded**: Functional but with reduced performance/reliability - - **unhealthy**: Significant issues affecting functionality - - **critical**: Service down or critically impaired - - 2. **Health Indicators**: - - Error rate - - Response time - - Resource usage (CPU, memory) - - Request success rate - - Availability - - 3. **Issue Identification**: - - What specific problems exist? - - How do they affect operations? - - Are they getting worse? - - 4. **Recommendations**: - - Immediate actions (if unhealthy/critical) - - Preventive measures - - Monitoring improvements - - 5. **Confidence**: - - High if logs and metrics clearly indicate status - - Lower if incomplete data or ambiguous signals - - 6. **Impact Prediction**: - - What happens if issues aren't resolved? - - User impact, data impact, system impact - - Provide comprehensive health assessment now. - - {{ ctx.output_format }} - "# -} - -/// Detect performance anomalies -/// -/// Identifies performance metrics that deviate from normal patterns, -/// helping catch issues before they become critical. -/// -/// # Arguments -/// - metric_name: Name of the metric to analyze -/// - current_value: Current value of the metric -/// - historical_values: Recent historical values for comparison -/// - baseline_description: Description of normal/expected values -/// -/// # Returns -/// PerformanceAnomaly if detected, or null if metric is normal -function DetectPerformanceAnomaly( - metric_name: string, - current_value: string, - historical_values: string[], - baseline_description: string -) -> PerformanceAnomaly | null { - client AgentdFast - prompt #" - Analyze this performance metric for anomalies: - - **Metric:** {{ metric_name }} - - **Current Value:** {{ current_value }} - - **Recent Historical Values:** - {% for value in historical_values %} - - {{ value }} - {% endfor %} - - **Baseline/Expected Behavior:** - {{ baseline_description }} - - **Anomaly Detection Guidelines:** - - 1. **Compare to Baseline**: - - Is current value within expected range? - - How much does it deviate (percentage)? - - 2. **Trend Analysis**: - - Is there a concerning trend in recent values? - - Sudden spike vs. gradual increase - - 3. **Severity**: - - **critical**: Value far outside normal, system at risk - - **high**: Significant deviation, requires attention - - **medium**: Notable deviation, should monitor - - **low**: Minor deviation, possibly normal variation - - 4. **Possible Causes**: - - Increased load? - - Resource exhaustion? - - Configuration change? - - External dependency issue? - - 5. **Investigation**: - - What should be checked first? - - What logs to review? - - What other metrics to correlate? - - **Important**: Return null if the metric appears normal (within expected range with no concerning trends). - Only return PerformanceAnomaly if you detect an actual anomaly. - - {{ ctx.output_format }} - "# -} - -/// Correlate errors across services -/// -/// Analyzes errors from multiple services to identify related failures, -/// cascading issues, or common root causes. -/// -/// # Arguments -/// - service_errors: Map of service names to their error messages -/// - time_window: Time period for correlation -/// -/// # Returns -/// String describing correlations and potential common causes -function CorrelateServiceErrors( - service_errors: map<string, string[]>, - time_window: string -) -> string { - client AgentdPrimary - prompt #" - Analyze these errors from multiple services to find correlations ({{ time_window }}): - - {{ service_errors }} - - **Correlation Analysis Guidelines:** - - 1. **Common Causes**: - - Do errors share common infrastructure (database, cache, network)? - - Are errors occurring at the same time? - - Do error messages reference the same resources? - - 2. **Cascade Detection**: - - Does one service failure trigger others? - - What's the sequence of failures? - - 3. **Root Cause Hypothesis**: - - What single issue could explain multiple failures? - - Infrastructure problem? - - Configuration change? - - Resource exhaustion? - - 4. **Impact Assessment**: - - Which service failed first? - - What's the blast radius? - - Provide a narrative analysis describing: - - What correlations you found - - Likely root cause(s) - - Which service to investigate first - - Recommended remediation approach - - {{ ctx.output_format }} - "# -} - -// ============================================================================ -// Tests -// ============================================================================ - -test analyze_error_logs { - functions [AnalyzeLogs] - args { - service_name "agentd-notify" - log_entries [ - "[ERROR] Failed to connect to database: connection refused", - "[ERROR] Retry attempt 1 failed: connection refused", - "[ERROR] Retry attempt 2 failed: connection refused", - "[WARN] Falling back to in-memory storage", - "[INFO] Service continuing with degraded functionality" - ] - time_window "last 5 minutes" - } -} - -test analyze_healthy_logs { - functions [AnalyzeLogs] - args { - service_name "agentd-ask" - log_entries [ - "[INFO] Service started on port 3001", - "[INFO] Connected to notification service", - "[INFO] Processed check request: tmux_sessions", - "[INFO] Health check passed" - ] - time_window "last 10 minutes" - } -} - -test detect_patterns_repetitive_error { - functions [DetectLogPatterns] - args { - log_entries [ - "10:00:00 [ERROR] API timeout: /users endpoint", - "10:05:00 [ERROR] API timeout: /users endpoint", - "10:10:00 [ERROR] API timeout: /users endpoint", - "10:15:00 [ERROR] API timeout: /users endpoint", - "10:20:00 [INFO] Request succeeded: /products endpoint", - "10:25:00 [ERROR] API timeout: /users endpoint" - ] - pattern_window "1 hour" - } -} - -test assess_healthy_service { - functions [AssessServiceHealth] - args { - service_name "agentd-notify" - recent_logs [ - "[INFO] Processed 150 notifications", - "[INFO] Database query completed in 23ms", - "[INFO] Health check passed" - ] - metrics "{\"error_rate\": 0.0, \"avg_response_time_ms\": 45, \"memory_usage_mb\": 128, \"active_connections\": 5}" - expected_behavior "Error rate < 1%, response time < 100ms, memory usage < 512MB" - } -} - -test assess_degraded_service { - functions [AssessServiceHealth] - args { - service_name "agentd-monitor" - recent_logs [ - "[WARN] High memory usage: 450MB", - "[WARN] Slow query: 2.5s", - "[ERROR] Connection pool exhausted", - "[INFO] Retry succeeded after 3 attempts" - ] - metrics "{\"error_rate\": 2.5, \"avg_response_time_ms\": 850, \"memory_usage_mb\": 450, \"active_connections\": 95}" - expected_behavior "Error rate < 1%, response time < 200ms, memory usage < 256MB, connections < 50" - } -} - -test detect_performance_anomaly { - functions [DetectPerformanceAnomaly] - args { - metric_name "api_response_time_ms" - current_value "1250ms" - historical_values ["45ms", "52ms", "48ms", "51ms", "47ms", "49ms", "46ms", "1248ms"] - baseline_description "Typically 40-60ms, 95th percentile < 100ms" - } -} - -test correlate_database_failure { - functions [CorrelateServiceErrors] - args { - service_errors { - "agentd-notify": [ - "Database connection failed", - "Query timeout after 30s" - ], - "agentd-ask": [ - "Cannot store question: database unavailable", - "Falling back to in-memory storage" - ], - "agentd-monitor": [ - "Database health check failed", - "Unable to persist metrics" - ] - } - time_window "10:00-10:05" - } -} diff --git a/baml_src/notifications.baml b/baml_src/notifications.baml deleted file mode 100644 index 581504ff..00000000 --- a/baml_src/notifications.baml +++ /dev/null @@ -1,288 +0,0 @@ -// Notification-related BAML functions for agentd -// -// This module provides AI-powered notification processing including: -// - Automatic categorization and prioritization -// - Notification summarization and digests -// - Smart filtering and grouping - -// ============================================================================ -// Data Models -// ============================================================================ - -/// Result of notification categorization -class NotificationCategory { - /// Category type: "urgent", "info", "action_required", "reminder", "system", "error" - category string - - /// Priority level: "low", "normal", "high", "urgent" - priority string - - /// Suggested lifetime: "ephemeral" (auto-dismiss), "persistent" (keep until dismissed) - suggested_lifetime string - - /// Brief explanation of the categorization decision - reasoning string - - /// Recommended action for the user (if any) - suggested_action string? -} - -/// Summary of multiple notifications for digest generation -class NotificationDigest { - /// High-level summary of notification activity - summary string - - /// Key actions that require user attention - key_actions string[] - - /// Count of notifications by priority - urgent_count int - high_count int - normal_count int - low_count int - - /// Distribution of notifications by category - categories map<string, int> - - /// Observed trends or patterns in notification activity - trends string - - /// Actionable recommendations for the user - recommendations string[] -} - -/// Notification grouping suggestion -class NotificationGroup { - /// Group title/name - title string - - /// IDs of notifications that should be grouped together - notification_ids string[] - - /// Reason for grouping - reasoning string - - /// Suggested group action (e.g., "mark all as read", "dismiss group") - suggested_group_action string? -} - -// ============================================================================ -// Functions -// ============================================================================ - -/// Categorize a notification automatically based on its content -/// -/// This function analyzes the notification title, message, and source context -/// to determine the appropriate category, priority, and lifetime. -/// -/// # Arguments -/// - title: The notification title -/// - message: The notification message body -/// - source_context: Context about where the notification came from -/// -/// # Returns -/// NotificationCategory with category, priority, lifetime, and reasoning -function CategorizeNotification( - title: string, - message: string, - source_context: string -) -> NotificationCategory { - client LocalOllama - prompt #" - Analyze this notification and determine its appropriate categorization: - - **Title:** {{ title }} - - **Message:** {{ message }} - - **Source:** {{ source_context }} - - Please categorize this notification by considering: - - **Category Guidelines:** - - "urgent": System failures, security issues, critical errors requiring immediate attention - - "action_required": User decisions needed, approvals, confirmations - - "error": Non-critical errors, warnings, issues that should be addressed - - "reminder": Time-based reminders, scheduled notifications - - "system": System status updates, background task completions - - "info": General information, FYI messages, status updates - - **Priority Guidelines:** - - "urgent": Immediate attention required, system down, data loss risk - - "high": Important but not critical, should be addressed soon - - "normal": Standard notifications, can be addressed during normal workflow - - "low": Informational only, no action required - - **Lifetime Guidelines:** - - "ephemeral": Auto-dismiss notifications that are time-sensitive or informational - - "persistent": Keep until user explicitly dismisses, for actionable items - - Provide your analysis in the structured format. - - {{ ctx.output_format }} - "# -} - -/// Generate a digest summarizing multiple notifications -/// -/// Creates a comprehensive summary of notification activity over a time period, -/// highlighting key actions, trends, and recommendations. -/// -/// # Arguments -/// - notifications: List of notification summaries (format: "title: message | source | priority") -/// - time_period: Description of the time period (e.g., "last 24 hours", "this week") -/// -/// # Returns -/// NotificationDigest with summary, key actions, trends, and recommendations -function SummarizeNotifications( - notifications: string[], - time_period: string -) -> NotificationDigest { - client AgentdPrimary - prompt #" - Summarize these notifications from {{ time_period }}: - - {% for notification in notifications %} - {{ loop.index }}. {{ notification }} - {% endfor %} - - Please provide: - - 1. **Summary**: A high-level overview of notification activity during this period - 2. **Key Actions**: Specific actions the user should take (be concrete and actionable) - 3. **Counts**: Accurate counts of notifications by priority (urgent, high, normal, low) - 4. **Categories**: Distribution of notifications by type - 5. **Trends**: Any patterns or trends in the notification activity - 6. **Recommendations**: Specific suggestions for the user based on the activity - - Focus on actionable insights rather than just repeating what the notifications say. - - {{ ctx.output_format }} - "# -} - -/// Suggest intelligent grouping of related notifications -/// -/// Analyzes multiple notifications to identify which ones should be grouped -/// together based on common themes, sources, or action items. -/// -/// # Arguments -/// - notifications: List of notifications with their IDs and content -/// - min_group_size: Minimum number of notifications to form a group (default: 2) -/// -/// # Returns -/// List of NotificationGroup objects with suggested groupings -function GroupRelatedNotifications( - notifications: map<string, string>, - min_group_size: int -) -> NotificationGroup[] { - client LocalOllama - prompt #" - Analyze these notifications and suggest intelligent groupings: - - {{ notifications }} - - **Grouping Rules:** - - Minimum group size: {{ min_group_size }} notifications - - Group by common theme, source, or related actions - - Each notification should belong to at most one group - - Don't force groupings if notifications are unrelated - - **Example groupings:** - - Multiple error notifications from the same service - - Series of status updates for a long-running task - - Related action items that can be handled together - - Return a list of suggested groups. Include only meaningful groupings. - - {{ ctx.output_format }} - "# -} - -/// Determine if a notification is still relevant -/// -/// Analyzes a notification's content, age, and current system state to determine -/// if it should be automatically dismissed or archived. -/// -/// # Arguments -/// - title: Notification title -/// - message: Notification message -/// - created_hours_ago: How many hours ago the notification was created -/// - current_system_state: Brief description of current system state -/// -/// # Returns -/// Boolean indicating if the notification is still relevant and should be kept -function IsNotificationStillRelevant( - title: string, - message: string, - created_hours_ago: int, - current_system_state: string -) -> bool { - client LocalOllama - prompt #" - Determine if this notification is still relevant: - - **Notification:** - Title: {{ title }} - Message: {{ message }} - Created: {{ created_hours_ago }} hours ago - - **Current System State:** - {{ current_system_state }} - - **Relevance Criteria:** - - Time-sensitive notifications become irrelevant after the event/deadline passes - - Error notifications become irrelevant if the error is resolved - - Status update notifications become irrelevant once superseded by newer updates - - Persistent action items remain relevant until explicitly completed - - Return true if the notification is still relevant and should be kept, - false if it can be safely archived or dismissed. - - Answer with just 'true' or 'false'. - "# -} - -// ============================================================================ -// Tests -// ============================================================================ - -test categorize_urgent_system_failure { - functions [CategorizeNotification] - args { - title "Database Connection Lost" - message "Unable to connect to PostgreSQL database. All services are affected." - source_context "production monitoring system" - } -} - -test categorize_normal_task_completion { - functions [CategorizeNotification] - args { - title "Backup Completed" - message "Daily backup finished successfully. 2.3GB backed up to cloud storage." - source_context "backup service" - } -} - -test categorize_action_required { - functions [CategorizeNotification] - args { - title "Approval Needed" - message "Please approve the deployment to production environment" - source_context "deployment pipeline" - } -} - -test summarize_daily_notifications { - functions [SummarizeNotifications] - args { - notifications [ - "Database backup completed | backup-service | normal", - "API response time degraded | monitoring | high", - "Deployment approved by admin | ci/cd | normal", - "Certificate expiring in 7 days | security | high" - ] - time_period "today" - } -} diff --git a/baml_src/questions.baml b/baml_src/questions.baml deleted file mode 100644 index edfa81d4..00000000 --- a/baml_src/questions.baml +++ /dev/null @@ -1,415 +0,0 @@ -// Question generation BAML functions for agentd ask service -// -// This module provides AI-powered question generation for the ask service, -// enabling contextual, intelligent questions based on system state and user behavior. - -// ============================================================================ -// Data Models -// ============================================================================ - -/// Generated system question with context and suggestions -class SystemQuestion { - /// The question text to present to the user - question_text string - - /// Suggested responses (e.g., ["yes", "no", "later"], or specific options) - suggested_responses string[] - - /// Brief explanation of why this question is being asked - reasoning string - - /// Urgency level: "low", "normal", "high", "urgent" - urgency string - - /// Follow-up actions the system might take based on user response - follow_up_actions map<string, string> - - /// Additional context or help text for the user - help_text string? -} - -/// Analysis of user's previous answer to improve future questions -class AnswerAnalysis { - /// Interpreted meaning of the user's answer - interpretation string - - /// Confidence in interpretation (0.0 - 1.0) - confidence float - - /// Suggested system action based on this answer - suggested_action string - - /// Whether a follow-up question might be needed - needs_followup bool - - /// Optional follow-up question if needed - followup_question string? -} - -/// Question effectiveness feedback for learning -class QuestionFeedback { - /// Whether the question was clear and actionable - was_clear bool - - /// Whether the user found the question relevant - was_relevant bool - - /// Suggestions for improving this question type - improvement_suggestions string[] - - /// Optimal time to ask this question (if timing was an issue) - better_timing string? -} - -// ============================================================================ -// Functions -// ============================================================================ - -/// Generate a context-aware system question -/// -/// Creates intelligent questions based on system state, user context, and history. -/// Used by the ask service to replace hardcoded question templates. -/// -/// # Arguments -/// - check_type: Type of system check (e.g., "tmux_sessions", "disk_space", "service_health") -/// - system_state: Current system state (e.g., "0 tmux sessions running", "80% disk usage") -/// - user_context: Information about user's current activity and preferences -/// - recent_history: Recent relevant events or user actions -/// -/// # Returns -/// SystemQuestion with question text, suggested responses, and follow-up actions -function GenerateSystemQuestion( - check_type: string, - system_state: string, - user_context: string, - recent_history: string -) -> SystemQuestion { - client AgentdPrimary - prompt #" - Generate a contextual question for the user based on system state. - - **Check Type:** {{ check_type }} - - **Current System State:** - {{ system_state }} - - **User Context:** - {{ user_context }} - - **Recent History:** - {{ recent_history }} - - **Guidelines for Question Generation:** - - 1. **Clarity**: Question should be specific and unambiguous - 2. **Actionability**: User should clearly understand what action to take - 3. **Context**: Reference relevant information from system state and history - 4. **Brevity**: Keep question concise, save details for help text - 5. **Suggestions**: Provide 2-5 suggested responses that cover common cases - - **Example Questions (for inspiration):** - - For tmux check: "No tmux sessions are running. Would you like to start your usual development session?" - - For disk space: "Disk usage is at 85%. Should I clean up old logs and temporary files?" - - For service health: "The notification service hasn't responded in 5 minutes. Should I restart it?" - - **Follow-up Actions:** - Map each suggested response to a concrete system action: - - What should happen if user says "yes"? - - What should happen if user says "no"? - - What about other responses? - - Generate the question structure now. - - {{ ctx.output_format }} - "# -} - -/// Analyze user's answer to a question -/// -/// Interprets the user's answer, extracting intent and determining next steps. -/// Handles various answer formats (yes/no, free-form text, etc.) -/// -/// # Arguments -/// - original_question: The question that was asked -/// - user_answer: The user's response -/// - expected_response_type: Type of expected response (e.g., "yes_no", "free_form", "choice") -/// -/// # Returns -/// AnswerAnalysis with interpretation, confidence, and suggested action -function AnalyzeAnswer( - original_question: string, - user_answer: string, - expected_response_type: string -) -> AnswerAnalysis { - client AgentdFast - prompt #" - Analyze the user's answer to determine intent and next steps. - - **Original Question:** - {{ original_question }} - - **User's Answer:** - "{{ user_answer }}" - - **Expected Response Type:** {{ expected_response_type }} - - **Analysis Guidelines:** - - 1. **Interpretation**: What is the user's intent? - - Handle variations (e.g., "yeah", "sure", "ok" all mean "yes") - - Detect uncertainty (e.g., "maybe", "I'm not sure") - - Recognize qualified answers (e.g., "yes, but only if...") - - 2. **Confidence**: How confident are you in the interpretation? - - High (0.9-1.0): Clear, unambiguous answer - - Medium (0.6-0.9): Reasonable interpretation, some ambiguity - - Low (0.0-0.6): Unclear answer, may need clarification - - 3. **Action**: What should the system do next? - - Be specific and actionable - - Consider edge cases - - Handle failure scenarios - - 4. **Follow-up**: Does this answer require additional clarification? - - Analyze the answer now. - - {{ ctx.output_format }} - "# -} - -/// Generate a clarifying follow-up question -/// -/// When a user's answer is ambiguous or unclear, generate a follow-up -/// question to clarify their intent. -/// -/// # Arguments -/// - original_question: The original question -/// - original_answer: The user's ambiguous answer -/// - ambiguity_reason: Why the answer needs clarification -/// -/// # Returns -/// SystemQuestion with clarifying question and updated context -function GenerateFollowUpQuestion( - original_question: string, - original_answer: string, - ambiguity_reason: string -) -> SystemQuestion { - client AgentdFast - prompt #" - Generate a clarifying follow-up question. - - **Original Question:** - {{ original_question }} - - **User's Answer:** - "{{ original_answer }}" - - **Why Clarification Needed:** - {{ ambiguity_reason }} - - **Guidelines:** - - Reference the original context - - Be specific about what needs clarification - - Offer concrete options - - Keep it brief and focused - - **Example:** - Original: "Should I restart the service?" - Answer: "maybe" - Follow-up: "I understand you're unsure. The service has been unresponsive for 10 minutes. Would you like me to: 1) Restart it now, 2) Wait 5 more minutes, or 3) Just alert you if it doesn't recover?" - - Generate the follow-up question now. - - {{ ctx.output_format }} - "# -} - -/// Evaluate question effectiveness for continuous improvement -/// -/// Analyzes how effective a question was based on user response patterns, -/// providing feedback for improving future questions. -/// -/// # Arguments -/// - question_text: The question that was asked -/// - response_time_seconds: How long it took the user to respond -/// - user_answer: The user's answer -/// - system_outcome: What happened after the answer (success/failure) -/// -/// # Returns -/// QuestionFeedback with evaluation and improvement suggestions -function EvaluateQuestionEffectiveness( - question_text: string, - response_time_seconds: int, - user_answer: string, - system_outcome: string -) -> QuestionFeedback { - client AgentdFast - prompt #" - Evaluate how effective this question was. - - **Question:** {{ question_text }} - - **User Response Time:** {{ response_time_seconds }} seconds - - **User's Answer:** "{{ user_answer }}" - - **System Outcome:** {{ system_outcome }} - - **Evaluation Criteria:** - - 1. **Clarity**: Was the question clear? - - Very quick response (< 10s) suggests clarity - - Very slow response (> 120s) might indicate confusion - - Non-standard answers might indicate unclear options - - 2. **Relevance**: Was the question timely and relevant? - - Successful outcome suggests good relevance - - Failed outcome might indicate poor timing or context - - 3. **Improvements**: How could this question be better? - - Different phrasing? - - Better timing? - - More/fewer response options? - - Additional context? - - Provide honest, actionable feedback. - - {{ ctx.output_format }} - "# -} - -/// Generate a personalized question based on user preferences -/// -/// Creates questions tailored to the user's communication style and preferences, -/// learned from previous interactions. -/// -/// # Arguments -/// - base_question: The basic question content -/// - user_preferences: User's learned preferences (verbosity, formality, etc.) -/// - interaction_history: Summary of past interactions -/// -/// # Returns -/// SystemQuestion adapted to user's preferences -function PersonalizeQuestion( - base_question: string, - user_preferences: string, - interaction_history: string -) -> SystemQuestion { - client AgentdFast - prompt #" - Personalize this question for the user. - - **Base Question:** - {{ base_question }} - - **User Preferences:** - {{ user_preferences }} - - **Interaction History:** - {{ interaction_history }} - - **Personalization Guidelines:** - - 1. **Tone Matching**: - - Formal user → Use professional language - - Casual user → Use conversational tone - - Technical user → Include technical details - - 2. **Verbosity**: - - Prefers brief → Short question, minimal help text - - Prefers detailed → Comprehensive context and options - - 3. **Response Style**: - - Quick decider → Binary choices (yes/no) - - Thoughtful → Multiple options with context - - 4. **Learning from History**: - - What question patterns work well for this user? - - What response types do they prefer? - - Adapt the question now. - - {{ ctx.output_format }} - "# -} - -// ============================================================================ -// Tests -// ============================================================================ - -test generate_tmux_session_question { - functions [GenerateSystemQuestion] - args { - check_type "tmux_sessions" - system_state "0 tmux sessions running, tmux is installed" - user_context "User is in terminal, last tmux session ended 2 hours ago" - recent_history "User typically runs 2-3 tmux sessions during work hours. Previous session was named 'dev-main'." - } -} - -test generate_service_health_question { - functions [GenerateSystemQuestion] - args { - check_type "service_health" - system_state "agentd-notify service not responding, last heartbeat 5 minutes ago" - user_context "User is actively using the system, notifications are enabled" - recent_history "Service has been stable for 3 days. No recent configuration changes." - } -} - -test analyze_clear_yes_answer { - functions [AnalyzeAnswer] - args { - original_question "Should I restart the notification service?" - user_answer "yes" - expected_response_type "yes_no" - } -} - -test analyze_ambiguous_answer { - functions [AnalyzeAnswer] - args { - original_question "Would you like to start a new tmux session?" - user_answer "maybe later" - expected_response_type "yes_no" - } -} - -test generate_followup_for_ambiguous { - functions [GenerateFollowUpQuestion] - args { - original_question "Should I clean up old log files? Disk is 85% full." - original_answer "depends how old" - ambiguity_reason "User's answer doesn't clearly indicate yes/no, needs age threshold" - } -} - -test evaluate_effective_question { - functions [EvaluateQuestionEffectiveness] - args { - question_text "Notification service is down. Restart it now?" - response_time_seconds 3 - user_answer "yes" - system_outcome "Service restarted successfully and is responding normally" - } -} - -test evaluate_ineffective_question { - functions [EvaluateQuestionEffectiveness] - args { - question_text "Something might be wrong. What should I do?" - response_time_seconds 45 - user_answer "I don't know, what's wrong?" - system_outcome "No action taken, user confused about the situation" - } -} - -test personalize_for_technical_user { - functions [PersonalizeQuestion] - args { - base_question "A service is not responding. Should I restart it?" - user_preferences "Technical user, prefers detailed information, uses technical terminology" - interaction_history "Usually asks for more details before deciding. Prefers seeing error codes and logs." - } -} diff --git a/crates/baml/Cargo.toml b/crates/baml/Cargo.toml deleted file mode 100644 index 32bc7c52..00000000 --- a/crates/baml/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "baml" -version.workspace = true -edition.workspace = true -authors.workspace = true -license.workspace = true - -[dependencies] -# HTTP client -reqwest = { workspace = true, features = ["json"] } - -# Async runtime -tokio = { workspace = true } - -# Serialization -serde = { workspace = true } -serde_json = { workspace = true } - -# Error handling -anyhow = { workspace = true } -thiserror = "2.0" - -# Logging -tracing = { workspace = true } - -[dev-dependencies] -tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } -tracing-subscriber = "0.3" -mockito = "1.2" diff --git a/crates/baml/README.md b/crates/baml/README.md deleted file mode 100644 index f1d8a02d..00000000 --- a/crates/baml/README.md +++ /dev/null @@ -1,488 +0,0 @@ -# BAML Rust Client - -Rust client for interacting with the BAML (Basically a Made-up Language) server running via `baml serve`. - -## Overview - -This crate provides a type-safe, idiomatic Rust interface to all BAML functions defined in `baml_src/`. The BAML server provides AI-powered intelligence for: - -- **Notifications**: Auto-categorization, digests, grouping -- **Questions**: Context-aware generation and analysis -- **Monitoring**: Log analysis, health assessment, anomaly detection -- **CLI**: Natural language parsing and help -- **Hooks**: Shell event analysis and filtering - -## Quick Start - -### 1. Start BAML Server - -```bash -# Ensure Ollama is running (for local LLM) -ollama serve -ollama pull llama3.2 - -# Start BAML server from project root -cd /path/to/agentd -baml serve -``` - -### 2. Use the Client - -```rust -use baml::{BamlClient, BamlClientConfig}; - -#[tokio::main] -async fn main() -> Result<(), Box<dyn std::error::Error>> { - // Create client - let client = BamlClient::default(); - - // Categorize a notification - let result = client.categorize_notification( - "Database Error", - "Connection failed to PostgreSQL", - "production monitoring" - ).await?; - - println!("Category: {}", result.category); - println!("Priority: {}", result.priority); - - Ok(()) -} -``` - -## Configuration - -### Default Configuration - -By default, the client connects to `http://localhost:2024` with a 30-second timeout and 2 retries. - -### Custom Configuration - -```rust -use baml::{BamlClient, BamlClientConfig}; - -let config = BamlClientConfig::new("http://baml-server:8080") - .with_timeout(60) // 60 second timeout - .with_max_retries(3); // retry up to 3 times - -let client = BamlClient::new(config); -``` - -### Environment-Based Configuration - -```rust -let base_url = std::env::var("BAML_SERVER_URL") - .unwrap_or_else(|_| "http://localhost:2024".to_string()); - -let config = BamlClientConfig::new(base_url); -let client = BamlClient::new(config); -``` - -## API Reference - -### Notification Functions - -```rust -// Auto-categorize notification -let category = client.categorize_notification( - "title", - "message", - "source" -).await?; - -// Generate digest -let digest = client.summarize_notifications( - &["notif1", "notif2"], - "last 24 hours" -).await?; - -// Group related notifications -let groups = client.group_related_notifications( - ¬ification_map, - 2 // min group size -).await?; - -// Check if notification still relevant -let relevant = client.is_notification_still_relevant( - "title", - "message", - 24, // hours ago - "current state" -).await?; -``` - -### Question Functions - -```rust -// Generate contextual question -let question = client.generate_system_question( - "check_type", - "system state", - "user context", - "recent history" -).await?; - -// Analyze user's answer -let analysis = client.analyze_answer( - "original question", - "user answer", - "yes_no" // expected type -).await?; - -// Generate follow-up question -let followup = client.generate_followup_question( - "original question", - "ambiguous answer", - "why clarification needed" -).await?; - -// Evaluate question effectiveness -let feedback = client.evaluate_question_effectiveness( - "question text", - 15, // response time seconds - "user answer", - "system outcome" -).await?; - -// Personalize question for user -let personalized = client.personalize_question( - "base question", - "user preferences", - "interaction history" -).await?; -``` - -### Monitoring Functions - -```rust -// Analyze logs for errors -let analysis = client.analyze_logs( - "service-name", - &log_entries, - "time window" -).await?; - -// Detect patterns in logs -let patterns = client.detect_log_patterns( - &log_entries, - "1 hour" -).await?; - -// Assess service health -let health = client.assess_service_health( - "service-name", - &recent_logs, - "metrics json", - "expected behavior" -).await?; - -// Detect performance anomaly -let anomaly = client.detect_performance_anomaly( - "metric_name", - "current_value", - &historical_values, - "baseline description" -).await?; - -// Correlate errors across services -let correlation = client.correlate_service_errors( - &service_error_map, - "time window" -).await?; -``` - -### CLI Functions - -```rust -// Parse natural language command -let intent = client.parse_natural_language_command( - "remind me about deployment", - "current context" -).await?; - -// Suggest command correction -let suggestion = client.suggest_command_correction( - "notif list", - "error message" -).await?; - -// Provide natural language help -let help = client.provide_natural_language_help( - "how do I create a notification?", - "available commands" -).await?; - -// Explain command before execution -let explanation = client.explain_command( - "agent notify delete --all", - "current context" -).await?; - -// Suggest command aliases -let aliases = client.suggest_command_aliases( - &command_history, - &frequency_map -).await?; -``` - -### Hook Functions - -```rust -// Analyze shell event -let action = client.analyze_shell_event( - "cargo build --release", - 0, // exit code - "output text", - 120000, // duration ms - "context" -).await?; - -// Learn command patterns -let patterns = client.learn_command_patterns( - &command_history, - ¬ification_history -).await?; - -// Analyze command intent -let insight = client.analyze_command_intent( - "npm run build", - "execution context" -).await?; - -// Generate completion notification -let notification = client.generate_completion_notification( - "cargo test", - 0, // exit code - 5000, // duration ms - "output summary", - 0 // previous attempts -).await?; - -// Filter relevant output -let filtered = client.filter_relevant_output( - "full command output", - 1, // exit code - 5 // max lines -).await?; -``` - -## Error Handling - -All methods return `Result<T, BamlError>`. Common errors: - -```rust -use baml::{BamlClient, BamlError}; - -match client.categorize_notification("title", "msg", "src").await { - Ok(result) => println!("Success: {:?}", result), - Err(BamlError::ServerUnreachable { url, .. }) => { - eprintln!("BAML server not running at {}", url); - eprintln!("Start it with: baml serve"); - } - Err(BamlError::Timeout { timeout_secs }) => { - eprintln!("Request timed out after {} seconds", timeout_secs); - } - Err(BamlError::FunctionNotFound { function_name }) => { - eprintln!("Function '{}' not found on server", function_name); - eprintln!("Check BAML definitions in baml_src/"); - } - Err(e) => eprintln!("Error: {}", e), -} -``` - -## Integration Examples - -### agentd-notify Service - -```rust -use baml::BamlClient; -use notify::types::CreateNotificationRequest; - -async fn create_notification_with_ai_categorization( - baml: &BamlClient, - req: CreateNotificationRequest -) -> Result<Notification, Error> { - // Use AI to determine priority if not specified - let category = baml.categorize_notification( - &req.title, - &req.message, - &format!("{:?}", req.source) - ).await?; - - // Override priority with AI suggestion - let priority = match category.priority.as_str() { - "urgent" => NotificationPriority::Urgent, - "high" => NotificationPriority::High, - "normal" => NotificationPriority::Normal, - _ => NotificationPriority::Low, - }; - - // Create notification with AI-determined priority - create_notification_internal(req.with_priority(priority)) -} -``` - -### agentd-ask Service - -```rust -use baml::BamlClient; - -async fn generate_smart_question( - baml: &BamlClient, - check_type: &str, - system_state: &str -) -> Result<Question, Error> { - // Generate contextual question using AI - let question = baml.generate_system_question( - check_type, - system_state, - "user context from history", - "recent relevant events" - ).await?; - - // Create question with AI-generated content - Question { - text: question.question_text, - suggested_responses: question.suggested_responses, - urgency: parse_urgency(&question.urgency), - } -} -``` - -### agentd-monitor Service - -```rust -use baml::BamlClient; - -async fn analyze_service_logs( - baml: &BamlClient, - service_name: &str, - logs: Vec<String> -) -> Result<(), Error> { - // Analyze logs with AI - let analysis = baml.analyze_logs( - service_name, - &logs, - "last 5 minutes" - ).await?; - - // Take action if errors detected - if analysis.has_errors { - eprintln!("Errors detected in {}: {}", service_name, analysis.error_summary); - - if analysis.requires_immediate_attention { - send_alert(&analysis)?; - } - - for action in &analysis.suggested_actions { - eprintln!("Suggested: {}", action); - } - } - - Ok(()) -} -``` - -## Examples - -See the `examples/` directory for complete working examples: - -```bash -# Run example (ensure BAML server is running first) -cargo run --example categorize_notification -``` - -## Testing - -### Unit Tests - -```bash -cargo test -``` - -### Integration Tests (requires running BAML server) - -```bash -# Terminal 1: Start BAML server -baml serve - -# Terminal 2: Run tests -cargo test -- --ignored -``` - -## Troubleshooting - -### Server Unreachable - -**Error:** `BAML server unreachable at http://localhost:2024` - -**Solution:** - -```bash -# Check if BAML server is running -curl http://localhost:2024/health - -# Start BAML server -cd /path/to/agentd -baml serve -``` - -### Function Not Found - -**Error:** `BAML function 'XYZ' not found on server` - -**Solutions:** - -1. Verify function exists in `baml_src/*.baml` -2. Restart BAML server to reload functions -3. Check for typos in function name - -### Timeout Errors - -**Error:** `Request timed out after 30 seconds` - -**Solutions:** - -1. Increase timeout: `config.with_timeout(60)` -2. Use faster model: `LocalOllamaFast` in `baml_src/clients.baml` -3. Reduce input size (fewer logs, shorter text) - -### Ollama Not Running - -**Error:** BAML server logs show connection errors to Ollama - -**Solution:** - -```bash -# Start Ollama -ollama serve - -# Pull model if not present -ollama pull llama3.2 -``` - -## Performance - -### Typical Latencies - -- **Local Ollama** (llama3.2): 200-300ms -- **Local Ollama** (qwen2.5:3b): 100-150ms -- **Cloud Fallback** (Claude Haiku): 200-400ms - -### Optimization Tips - -1. Use `AgentdFast` client for low-latency operations -2. Implement caching for repeated requests -3. Batch operations where possible -4. Use smaller models for simple tasks - -## See Also - -- [BAML Documentation](https://docs.boundaryml.com/home) -- [BAML Source Definitions](../../baml_src/) -- [BAML Research Document](../../docs/research/baml-investigation.md) -- [Project README](../../README.md) - -## License - -MIT OR Apache-2.0 diff --git a/crates/baml/examples/categorize_notification.rs b/crates/baml/examples/categorize_notification.rs deleted file mode 100644 index 3ca31f8a..00000000 --- a/crates/baml/examples/categorize_notification.rs +++ /dev/null @@ -1,121 +0,0 @@ -//! Example: Categorize a notification using BAML -//! -//! This example shows how to use the BAML client to automatically categorize -//! a notification based on its content. -//! -//! # Prerequisites -//! -//! 1. Start Ollama (if using local LLM): -//! ```bash -//! ollama serve -//! ollama pull llama3.2 -//! ``` -//! -//! 2. Start BAML server: -//! ```bash -//! cd /path/to/agentd -//! baml serve -//! ``` -//! -//! # Running -//! -//! ```bash -//! cargo run --example categorize_notification -//! ``` - -use baml::{BamlClient, BamlClientConfig}; - -#[tokio::main] -async fn main() -> Result<(), Box<dyn std::error::Error>> { - // Initialize tracing for debug output - tracing_subscriber::fmt::init(); - - // Create BAML client (connects to localhost:2024 by default) - let config = BamlClientConfig::default(); - let client = BamlClient::new(config); - - println!("BAML Notification Categorization Example"); - println!("=========================================\n"); - - // Example 1: Critical system error - println!("Example 1: Critical system error"); - let result = client - .categorize_notification( - "Database Connection Lost", - "Unable to connect to PostgreSQL database at db.example.com:5432. \ - Connection timeout after 30 seconds. All services are affected.", - "production monitoring system", - ) - .await?; - - println!(" Title: Database Connection Lost"); - println!(" Category: {}", result.category); - println!(" Priority: {}", result.priority); - println!(" Lifetime: {}", result.suggested_lifetime); - println!(" Reasoning: {}", result.reasoning); - if let Some(action) = &result.suggested_action { - println!(" Suggested Action: {}", action); - } - println!(); - - // Example 2: Normal informational notification - println!("Example 2: Normal informational notification"); - let result = client - .categorize_notification( - "Backup Completed Successfully", - "Daily backup finished at 2:00 AM. 2.3GB backed up to S3. \ - All files verified successfully.", - "backup service", - ) - .await?; - - println!(" Title: Backup Completed Successfully"); - println!(" Category: {}", result.category); - println!(" Priority: {}", result.priority); - println!(" Lifetime: {}", result.suggested_lifetime); - println!(" Reasoning: {}", result.reasoning); - println!(); - - // Example 3: Action required - println!("Example 3: Action required"); - let result = client - .categorize_notification( - "Approval Needed for Production Deployment", - "The deployment pipeline has prepared version 2.1.0 for production. \ - Please review and approve before 5 PM today.", - "CI/CD pipeline", - ) - .await?; - - println!(" Title: Approval Needed for Production Deployment"); - println!(" Category: {}", result.category); - println!(" Priority: {}", result.priority); - println!(" Lifetime: {}", result.suggested_lifetime); - println!(" Reasoning: {}", result.reasoning); - if let Some(action) = &result.suggested_action { - println!(" Suggested Action: {}", action); - } - println!(); - - // Example 4: Warning about approaching limit - println!("Example 4: Warning about approaching limit"); - let result = client - .categorize_notification( - "Certificate Expiring Soon", - "SSL certificate for api.example.com will expire in 7 days. \ - Renewal is recommended to avoid service disruption.", - "security monitoring", - ) - .await?; - - println!(" Title: Certificate Expiring Soon"); - println!(" Category: {}", result.category); - println!(" Priority: {}", result.priority); - println!(" Lifetime: {}", result.suggested_lifetime); - println!(" Reasoning: {}", result.reasoning); - println!(); - - println!("✓ All examples completed successfully!"); - - Ok(()) -} diff --git a/crates/baml/src/client.rs b/crates/baml/src/client.rs deleted file mode 100644 index edc2069a..00000000 --- a/crates/baml/src/client.rs +++ /dev/null @@ -1,980 +0,0 @@ -//! BAML client for making requests to the BAML server. - -use crate::error::{BamlError, Result}; -use crate::types::*; -use reqwest::{Client as HttpClient, StatusCode}; -use serde::de::DeserializeOwned; -use serde::Serialize; -use std::collections::HashMap; -use std::time::Duration; -use tracing::debug; - -/// Configuration for the BAML client -#[derive(Debug, Clone)] -pub struct BamlClientConfig { - /// Base URL of the BAML server (e.g., "http://localhost:2024") - pub base_url: String, - /// Timeout for requests in seconds (default: 30) - pub timeout_secs: u64, - /// Number of retries for failed requests (default: 2) - pub max_retries: u32, -} - -impl Default for BamlClientConfig { - fn default() -> Self { - Self { base_url: "http://localhost:2024".to_string(), timeout_secs: 30, max_retries: 2 } - } -} - -impl BamlClientConfig { - /// Create a new config with the given base URL - pub fn new(base_url: impl Into<String>) -> Self { - Self { base_url: base_url.into(), ..Default::default() } - } - - /// Set the timeout in seconds - pub fn with_timeout(mut self, timeout_secs: u64) -> Self { - self.timeout_secs = timeout_secs; - self - } - - /// Set the maximum number of retries - pub fn with_max_retries(mut self, max_retries: u32) -> Self { - self.max_retries = max_retries; - self - } -} - -/// Client for interacting with BAML server functions -/// -/// The BAML server must be running (via `baml serve`) for this client to work. -/// -/// # Examples -/// -/// ```no_run -/// use baml::{BamlClient, BamlClientConfig}; -/// -/// #[tokio::main] -/// async fn main() -> Result<(), Box<dyn std::error::Error>> { -/// let config = BamlClientConfig::new("http://localhost:2024"); -/// let client = BamlClient::new(config); -/// -/// let result = client.categorize_notification( -/// "Database Error", -/// "Connection failed", -/// "production" -/// ).await?; -/// -/// println!("Category: {}", result.category); -/// Ok(()) -/// } -/// ``` -pub struct BamlClient { - config: BamlClientConfig, - http_client: HttpClient, -} - -impl Default for BamlClient { - fn default() -> Self { - Self::new(BamlClientConfig::default()) - } -} - -impl BamlClient { - /// Create a new BAML client with the given configuration - pub fn new(config: BamlClientConfig) -> Self { - let http_client = HttpClient::builder() - .timeout(Duration::from_secs(config.timeout_secs)) - .build() - .expect("Failed to build HTTP client"); - - Self { config, http_client } - } - - /// Call a BAML function with the given parameters - async fn call_function<P, R>(&self, function_name: &str, params: &P) -> Result<R> - where - P: Serialize, - R: DeserializeOwned, - { - let url = format!("{}/call/{}", self.config.base_url, function_name); - debug!("Calling BAML function: {}", function_name); - - let mut last_error = None; - - for attempt in 0..=self.config.max_retries { - if attempt > 0 { - debug!("Retry attempt {} for {}", attempt, function_name); - } - - match self.http_client.post(&url).json(params).send().await { - Ok(response) => { - let status = response.status(); - - if status.is_success() { - let response_text = response.text().await?; - let result: R = serde_json::from_str(&response_text).map_err(|e| { - BamlError::InvalidResponse(format!( - "Failed to parse response: {}. Response was: {}", - e, response_text - )) - })?; - return Ok(result); - } else if status == StatusCode::NOT_FOUND { - return Err(BamlError::FunctionNotFound { - function_name: function_name.to_string(), - }); - } else { - let error_body = response.text().await.unwrap_or_default(); - last_error = Some(BamlError::ServerError { - status: status.as_u16(), - message: error_body, - }); - } - } - Err(e) if e.is_timeout() => { - last_error = - Some(BamlError::Timeout { timeout_secs: self.config.timeout_secs }); - } - Err(e) if e.is_connect() => { - last_error = Some(BamlError::ServerUnreachable { url: url.clone(), source: e }); - } - Err(e) => { - last_error = Some(BamlError::RequestFailed(e)); - } - } - - if attempt < self.config.max_retries { - tokio::time::sleep(Duration::from_millis(100 * (attempt as u64 + 1))).await; - } - } - - Err(last_error.unwrap_or_else(|| BamlError::InvalidResponse("Unknown error".to_string()))) - } - - // ======================================================================== - // Notification Functions - // ======================================================================== - - /// Categorize a notification automatically based on its content - /// - /// # Arguments - /// * `title` - The notification title - /// * `message` - The notification message body - /// * `source_context` - Context about where the notification came from - /// - /// # Example - /// ```no_run - /// # use baml::BamlClient; - /// # async fn example(client: BamlClient) -> Result<(), Box<dyn std::error::Error>> { - /// let result = client.categorize_notification( - /// "Database Connection Lost", - /// "Unable to connect to PostgreSQL", - /// "production monitoring" - /// ).await?; - /// println!("Priority: {}", result.priority); - /// # Ok(()) - /// # } - /// ``` - pub async fn categorize_notification( - &self, - title: &str, - message: &str, - source_context: &str, - ) -> Result<NotificationCategory> { - #[derive(Serialize)] - struct Params<'a> { - title: &'a str, - message: &'a str, - source_context: &'a str, - } - - self.call_function("CategorizeNotification", &Params { title, message, source_context }) - .await - } - - /// Generate a digest summarizing multiple notifications - /// - /// # Arguments - /// * `notifications` - List of notification summaries - /// * `time_period` - Description of the time period (e.g., "last 24 hours") - pub async fn summarize_notifications( - &self, - notifications: &[String], - time_period: &str, - ) -> Result<NotificationDigest> { - #[derive(Serialize)] - struct Params<'a> { - notifications: &'a [String], - time_period: &'a str, - } - - self.call_function("SummarizeNotifications", &Params { notifications, time_period }).await - } - - /// Suggest intelligent grouping of related notifications - /// - /// # Arguments - /// * `notifications` - Map of notification IDs to their content - /// * `min_group_size` - Minimum number of notifications to form a group - pub async fn group_related_notifications( - &self, - notifications: &HashMap<String, String>, - min_group_size: i32, - ) -> Result<Vec<NotificationGroup>> { - #[derive(Serialize)] - struct Params<'a> { - notifications: &'a HashMap<String, String>, - min_group_size: i32, - } - - self.call_function("GroupRelatedNotifications", &Params { notifications, min_group_size }) - .await - } - - /// Determine if a notification is still relevant - /// - /// # Arguments - /// * `title` - Notification title - /// * `message` - Notification message - /// * `created_hours_ago` - How many hours ago the notification was created - /// * `current_system_state` - Brief description of current system state - pub async fn is_notification_still_relevant( - &self, - title: &str, - message: &str, - created_hours_ago: i32, - current_system_state: &str, - ) -> Result<bool> { - #[derive(Serialize)] - struct Params<'a> { - title: &'a str, - message: &'a str, - created_hours_ago: i32, - current_system_state: &'a str, - } - - self.call_function( - "IsNotificationStillRelevant", - &Params { title, message, created_hours_ago, current_system_state }, - ) - .await - } - - // ======================================================================== - // Question Functions - // ======================================================================== - - /// Generate a context-aware system question - /// - /// # Arguments - /// * `check_type` - Type of system check (e.g., "tmux_sessions") - /// * `system_state` - Current system state - /// * `user_context` - Information about user's current activity - /// * `recent_history` - Recent relevant events or user actions - pub async fn generate_system_question( - &self, - check_type: &str, - system_state: &str, - user_context: &str, - recent_history: &str, - ) -> Result<SystemQuestion> { - #[derive(Serialize)] - struct Params<'a> { - check_type: &'a str, - system_state: &'a str, - user_context: &'a str, - recent_history: &'a str, - } - - self.call_function( - "GenerateSystemQuestion", - &Params { check_type, system_state, user_context, recent_history }, - ) - .await - } - - /// Analyze user's answer to a question - /// - /// # Arguments - /// * `original_question` - The question that was asked - /// * `user_answer` - The user's response - /// * `expected_response_type` - Type of expected response (e.g., "yes_no") - pub async fn analyze_answer( - &self, - original_question: &str, - user_answer: &str, - expected_response_type: &str, - ) -> Result<AnswerAnalysis> { - #[derive(Serialize)] - struct Params<'a> { - original_question: &'a str, - user_answer: &'a str, - expected_response_type: &'a str, - } - - self.call_function( - "AnalyzeAnswer", - &Params { original_question, user_answer, expected_response_type }, - ) - .await - } - - /// Generate a clarifying follow-up question - /// - /// # Arguments - /// * `original_question` - The original question - /// * `original_answer` - The user's ambiguous answer - /// * `ambiguity_reason` - Why the answer needs clarification - pub async fn generate_followup_question( - &self, - original_question: &str, - original_answer: &str, - ambiguity_reason: &str, - ) -> Result<SystemQuestion> { - #[derive(Serialize)] - struct Params<'a> { - original_question: &'a str, - original_answer: &'a str, - ambiguity_reason: &'a str, - } - - self.call_function( - "GenerateFollowUpQuestion", - &Params { original_question, original_answer, ambiguity_reason }, - ) - .await - } - - /// Evaluate question effectiveness for continuous improvement - pub async fn evaluate_question_effectiveness( - &self, - question_text: &str, - response_time_seconds: i32, - user_answer: &str, - system_outcome: &str, - ) -> Result<QuestionFeedback> { - #[derive(Serialize)] - struct Params<'a> { - question_text: &'a str, - response_time_seconds: i32, - user_answer: &'a str, - system_outcome: &'a str, - } - - self.call_function( - "EvaluateQuestionEffectiveness", - &Params { question_text, response_time_seconds, user_answer, system_outcome }, - ) - .await - } - - /// Generate a personalized question based on user preferences - pub async fn personalize_question( - &self, - base_question: &str, - user_preferences: &str, - interaction_history: &str, - ) -> Result<SystemQuestion> { - #[derive(Serialize)] - struct Params<'a> { - base_question: &'a str, - user_preferences: &'a str, - interaction_history: &'a str, - } - - self.call_function( - "PersonalizeQuestion", - &Params { base_question, user_preferences, interaction_history }, - ) - .await - } - - // ======================================================================== - // Monitoring Functions - // ======================================================================== - - /// Analyze log entries for errors and issues - /// - /// # Arguments - /// * `service_name` - Name of the service generating the logs - /// * `log_entries` - Array of log lines to analyze - /// * `time_window` - Time period these logs cover - pub async fn analyze_logs( - &self, - service_name: &str, - log_entries: &[String], - time_window: &str, - ) -> Result<LogAnalysis> { - #[derive(Serialize)] - struct Params<'a> { - service_name: &'a str, - log_entries: &'a [String], - time_window: &'a str, - } - - self.call_function("AnalyzeLogs", &Params { service_name, log_entries, time_window }).await - } - - /// Detect patterns in log data - pub async fn detect_log_patterns( - &self, - log_entries: &[String], - pattern_window: &str, - ) -> Result<Vec<LogPattern>> { - #[derive(Serialize)] - struct Params<'a> { - log_entries: &'a [String], - pattern_window: &'a str, - } - - self.call_function("DetectLogPatterns", &Params { log_entries, pattern_window }).await - } - - /// Assess overall health of a service - pub async fn assess_service_health( - &self, - service_name: &str, - recent_logs: &[String], - metrics: &str, - expected_behavior: &str, - ) -> Result<ServiceHealth> { - #[derive(Serialize)] - struct Params<'a> { - service_name: &'a str, - recent_logs: &'a [String], - metrics: &'a str, - expected_behavior: &'a str, - } - - self.call_function( - "AssessServiceHealth", - &Params { service_name, recent_logs, metrics, expected_behavior }, - ) - .await - } - - /// Detect performance anomalies - pub async fn detect_performance_anomaly( - &self, - metric_name: &str, - current_value: &str, - historical_values: &[String], - baseline_description: &str, - ) -> Result<Option<PerformanceAnomaly>> { - #[derive(Serialize)] - struct Params<'a> { - metric_name: &'a str, - current_value: &'a str, - historical_values: &'a [String], - baseline_description: &'a str, - } - - self.call_function( - "DetectPerformanceAnomaly", - &Params { metric_name, current_value, historical_values, baseline_description }, - ) - .await - } - - /// Correlate errors across services - pub async fn correlate_service_errors( - &self, - service_errors: &HashMap<String, Vec<String>>, - time_window: &str, - ) -> Result<String> { - #[derive(Serialize)] - struct Params<'a> { - service_errors: &'a HashMap<String, Vec<String>>, - time_window: &'a str, - } - - self.call_function("CorrelateServiceErrors", &Params { service_errors, time_window }).await - } - - // ======================================================================== - // CLI Functions - // ======================================================================== - - /// Parse natural language input into a structured command - pub async fn parse_natural_language_command( - &self, - user_input: &str, - current_context: &str, - ) -> Result<CommandIntent> { - #[derive(Serialize)] - struct Params<'a> { - user_input: &'a str, - current_context: &'a str, - } - - self.call_function("ParseNaturalLanguageCommand", &Params { user_input, current_context }) - .await - } - - /// Suggest command corrections or completions - pub async fn suggest_command_correction( - &self, - user_input: &str, - error_message: &str, - ) -> Result<CommandSuggestion> { - #[derive(Serialize)] - struct Params<'a> { - user_input: &'a str, - error_message: &'a str, - } - - self.call_function("SuggestCommandCorrection", &Params { user_input, error_message }).await - } - - /// Provide help for natural language queries - pub async fn provide_natural_language_help( - &self, - user_question: &str, - available_commands: &str, - ) -> Result<HelpResponse> { - #[derive(Serialize)] - struct Params<'a> { - user_question: &'a str, - available_commands: &'a str, - } - - self.call_function( - "ProvideNaturalLanguageHelp", - &Params { user_question, available_commands }, - ) - .await - } - - /// Explain what a command will do before execution - pub async fn explain_command( - &self, - command_string: &str, - current_context: &str, - ) -> Result<String> { - #[derive(Serialize)] - struct Params<'a> { - command_string: &'a str, - current_context: &'a str, - } - - self.call_function("ExplainCommand", &Params { command_string, current_context }).await - } - - /// Generate command aliases or shortcuts - pub async fn suggest_command_aliases( - &self, - command_history: &[String], - frequency_map: &HashMap<String, i32>, - ) -> Result<HashMap<String, String>> { - #[derive(Serialize)] - struct Params<'a> { - command_history: &'a [String], - frequency_map: &'a HashMap<String, i32>, - } - - self.call_function("SuggestCommandAliases", &Params { command_history, frequency_map }) - .await - } - - // ======================================================================== - // Hook Functions - // ======================================================================== - - /// Analyze a shell event to determine if notification is needed - pub async fn analyze_shell_event( - &self, - command: &str, - exit_code: i32, - output: &str, - duration_ms: i32, - context: &str, - ) -> Result<HookAction> { - #[derive(Serialize)] - struct Params<'a> { - command: &'a str, - exit_code: i32, - output: &'a str, - duration_ms: i32, - context: &'a str, - } - - self.call_function( - "AnalyzeShellEvent", - &Params { command, exit_code, output, duration_ms, context }, - ) - .await - } - - /// Learn patterns from command history - pub async fn learn_command_patterns( - &self, - command_history: &[String], - notification_history: &[String], - ) -> Result<Vec<HookPattern>> { - #[derive(Serialize)] - struct Params<'a> { - command_history: &'a [String], - notification_history: &'a [String], - } - - self.call_function( - "LearnCommandPatterns", - &Params { command_history, notification_history }, - ) - .await - } - - /// Provide insights about a command before execution - pub async fn analyze_command_intent( - &self, - command: &str, - execution_context: &str, - ) -> Result<CommandInsight> { - #[derive(Serialize)] - struct Params<'a> { - command: &'a str, - execution_context: &'a str, - } - - self.call_function("AnalyzeCommandIntent", &Params { command, execution_context }).await - } - - /// Generate smart notification content for command completion - pub async fn generate_completion_notification( - &self, - command: &str, - exit_code: i32, - duration_ms: i32, - output_summary: &str, - previous_attempts: i32, - ) -> Result<String> { - #[derive(Serialize)] - struct Params<'a> { - command: &'a str, - exit_code: i32, - duration_ms: i32, - output_summary: &'a str, - previous_attempts: i32, - } - - self.call_function( - "GenerateCompletionNotification", - &Params { command, exit_code, duration_ms, output_summary, previous_attempts }, - ) - .await - } - - /// Filter out noise from command output - pub async fn filter_relevant_output( - &self, - full_output: &str, - exit_code: i32, - max_lines: i32, - ) -> Result<String> { - #[derive(Serialize)] - struct Params<'a> { - full_output: &'a str, - exit_code: i32, - max_lines: i32, - } - - self.call_function("FilterRelevantOutput", &Params { full_output, exit_code, max_lines }) - .await - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::error::BamlError; - use mockito::Server; - - fn client_with_url(url: &str) -> BamlClient { - let config = BamlClientConfig::new(url).with_timeout(5).with_max_retries(0); - BamlClient::new(config) - } - - fn client_with_retries(url: &str, retries: u32) -> BamlClient { - let config = BamlClientConfig::new(url).with_timeout(5).with_max_retries(retries); - BamlClient::new(config) - } - - // -- Config tests -- - - #[test] - fn test_default_config() { - let config = BamlClientConfig::default(); - assert_eq!(config.base_url, "http://localhost:2024"); - assert_eq!(config.timeout_secs, 30); - assert_eq!(config.max_retries, 2); - } - - #[test] - fn test_config_builder() { - let config = - BamlClientConfig::new("http://example.com").with_timeout(10).with_max_retries(5); - assert_eq!(config.base_url, "http://example.com"); - assert_eq!(config.timeout_secs, 10); - assert_eq!(config.max_retries, 5); - } - - #[test] - fn test_default_client() { - let _client = BamlClient::default(); - } - - // -- call_function success -- - - #[tokio::test] - async fn test_call_function_success() { - let mut server = Server::new_async().await; - let mock = server - .mock("POST", "/call/CategorizeNotification") - .with_status(200) - .with_header("content-type", "application/json") - .with_body( - r#"{ - "category": "error", - "priority": "high", - "suggested_lifetime": "persistent", - "reasoning": "Database error detected", - "suggested_action": "Check database connectivity" - }"#, - ) - .create_async() - .await; - - let client = client_with_url(&server.url()); - let result = - client.categorize_notification("DB Error", "Connection lost", "production").await; - - assert!(result.is_ok()); - let category = result.unwrap(); - assert_eq!(category.category, "error"); - assert_eq!(category.priority, "high"); - mock.assert_async().await; - } - - // -- Error handling -- - - #[tokio::test] - async fn test_function_not_found() { - let mut server = Server::new_async().await; - let mock = - server.mock("POST", "/call/NonExistentFunction").with_status(404).create_async().await; - - let client = client_with_url(&server.url()); - let result: Result<String> = client.call_function("NonExistentFunction", &"{}").await; - - assert!(result.is_err()); - assert!(matches!(result.unwrap_err(), BamlError::FunctionNotFound { .. })); - mock.assert_async().await; - } - - #[tokio::test] - async fn test_server_error_500() { - let mut server = Server::new_async().await; - let mock = server - .mock("POST", "/call/TestFunc") - .with_status(500) - .with_body("Internal server error") - .create_async() - .await; - - let client = client_with_url(&server.url()); - let result: Result<String> = client.call_function("TestFunc", &"{}").await; - - assert!(result.is_err()); - assert!(matches!(result.unwrap_err(), BamlError::ServerError { status: 500, .. })); - mock.assert_async().await; - } - - #[tokio::test] - async fn test_invalid_json_response() { - let mut server = Server::new_async().await; - let mock = server - .mock("POST", "/call/CategorizeNotification") - .with_status(200) - .with_body("not valid json") - .create_async() - .await; - - let client = client_with_url(&server.url()); - let result = client.categorize_notification("Test", "Test", "test").await; - - assert!(result.is_err()); - assert!(matches!(result.unwrap_err(), BamlError::InvalidResponse(_))); - mock.assert_async().await; - } - - #[tokio::test] - async fn test_server_unreachable() { - let client = client_with_url("http://127.0.0.1:19999"); - let result = client.categorize_notification("Test", "Test", "test").await; - - assert!(result.is_err()); - } - - // -- Retry logic -- - - #[tokio::test] - async fn test_retry_on_server_error() { - let mut server = Server::new_async().await; - - // First call returns 500, second returns 200 - let fail_mock = server - .mock("POST", "/call/TestFunc") - .with_status(500) - .with_body("temporary error") - .expect(1) - .create_async() - .await; - - let success_mock = server - .mock("POST", "/call/TestFunc") - .with_status(200) - .with_body("\"success\"") - .expect(1) - .create_async() - .await; - - let client = client_with_retries(&server.url(), 1); - let result: Result<String> = client.call_function("TestFunc", &"{}").await; - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), "success"); - fail_mock.assert_async().await; - success_mock.assert_async().await; - } - - #[tokio::test] - async fn test_no_retry_on_404() { - let mut server = Server::new_async().await; - - let mock = - server.mock("POST", "/call/Missing").with_status(404).expect(1).create_async().await; - - let client = client_with_retries(&server.url(), 2); - let result: Result<String> = client.call_function("Missing", &"{}").await; - - assert!(result.is_err()); - assert!(matches!(result.unwrap_err(), BamlError::FunctionNotFound { .. })); - mock.assert_async().await; - } - - #[tokio::test] - async fn test_exhausts_retries() { - let mut server = Server::new_async().await; - - let mock = server - .mock("POST", "/call/TestFunc") - .with_status(500) - .with_body("persistent error") - .expect(3) // initial + 2 retries - .create_async() - .await; - - let client = client_with_retries(&server.url(), 2); - let result: Result<String> = client.call_function("TestFunc", &"{}").await; - - assert!(result.is_err()); - assert!(matches!(result.unwrap_err(), BamlError::ServerError { .. })); - mock.assert_async().await; - } - - // -- Domain method tests -- - - #[tokio::test] - async fn test_summarize_notifications() { - let mut server = Server::new_async().await; - let mock = server - .mock("POST", "/call/SummarizeNotifications") - .with_status(200) - .with_body( - r#"{ - "summary": "5 notifications in the last hour", - "key_actions": ["Review database alerts"], - "urgent_count": 1, - "high_count": 2, - "normal_count": 1, - "low_count": 1, - "categories": {"error": 3, "info": 2}, - "trends": "Increasing error rate", - "recommendations": ["Check database"] - }"#, - ) - .create_async() - .await; - - let client = client_with_url(&server.url()); - let notifications = vec!["Alert 1".to_string(), "Alert 2".to_string()]; - let result = client.summarize_notifications(¬ifications, "last hour").await; - - assert!(result.is_ok()); - let digest = result.unwrap(); - assert_eq!(digest.urgent_count, 1); - assert_eq!(digest.high_count, 2); - mock.assert_async().await; - } - - #[tokio::test] - async fn test_analyze_answer() { - let mut server = Server::new_async().await; - let mock = server - .mock("POST", "/call/AnalyzeAnswer") - .with_status(200) - .with_body( - r#"{ - "interpretation": "User agrees", - "confidence": 0.95, - "suggested_action": "proceed", - "needs_followup": false, - "followup_question": null - }"#, - ) - .create_async() - .await; - - let client = client_with_url(&server.url()); - let result = client.analyze_answer("Start tmux?", "yes", "yes_no").await; - - assert!(result.is_ok()); - let analysis = result.unwrap(); - assert_eq!(analysis.confidence, 0.95); - assert!(!analysis.needs_followup); - mock.assert_async().await; - } - - #[tokio::test] - async fn test_analyze_shell_event() { - let mut server = Server::new_async().await; - let mock = server - .mock("POST", "/call/AnalyzeShellEvent") - .with_status(200) - .with_body( - r#"{ - "should_notify": true, - "notification_title": "Build Failed", - "notification_message": "cargo build exited with code 1", - "priority": "high", - "reasoning": "Build failure detected", - "metadata": {}, - "indicates_problem": true, - "suggested_actions": ["Fix compilation errors"] - }"#, - ) - .create_async() - .await; - - let client = client_with_url(&server.url()); - let result = - client.analyze_shell_event("cargo build", 1, "error[E0308]", 5000, "development").await; - - assert!(result.is_ok()); - let action = result.unwrap(); - assert!(action.should_notify); - assert!(action.indicates_problem); - mock.assert_async().await; - } -} diff --git a/crates/baml/src/error.rs b/crates/baml/src/error.rs deleted file mode 100644 index 98e6a431..00000000 --- a/crates/baml/src/error.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! Error types for the BAML client. - -use thiserror::Error; - -/// Errors that can occur when interacting with the BAML server -#[derive(Error, Debug)] -pub enum BamlError { - /// The BAML server is not reachable - #[error("BAML server unreachable at {url}: {source}")] - ServerUnreachable { url: String, source: reqwest::Error }, - - /// HTTP request failed - #[error("HTTP request failed: {0}")] - RequestFailed(#[from] reqwest::Error), - - /// JSON serialization/deserialization error - #[error("JSON error: {0}")] - JsonError(#[from] serde_json::Error), - - /// Server returned an error response - #[error("BAML server error (HTTP {status}): {message}")] - ServerError { status: u16, message: String }, - - /// Function not found on server - #[error("BAML function '{function_name}' not found on server")] - FunctionNotFound { function_name: String }, - - /// Invalid response from server - #[error("Invalid response from server: {0}")] - InvalidResponse(String), - - /// Timeout waiting for response - #[error("Request timed out after {timeout_secs} seconds")] - Timeout { timeout_secs: u64 }, - - /// Configuration error - #[error("Configuration error: {0}")] - ConfigError(String), -} - -/// Result type for BAML operations -pub type Result<T> = std::result::Result<T, BamlError>; diff --git a/crates/baml/src/lib.rs b/crates/baml/src/lib.rs deleted file mode 100644 index 8f113f87..00000000 --- a/crates/baml/src/lib.rs +++ /dev/null @@ -1,138 +0,0 @@ -//! Rust client for BAML (Basically a Made-up Language) server. -//! -//! This crate provides a type-safe Rust client for interacting with a BAML server -//! running via `baml serve`. The BAML server provides AI-powered functions for -//! intelligent automation throughout the agentd project. -//! -//! # Features -//! -//! - **Notification Intelligence**: Auto-categorization, digests, and relevance filtering -//! - **Smart Questions**: Context-aware question generation for the ask service -//! - **Log Analysis**: Automated error detection and root cause analysis -//! - **CLI Intelligence**: Natural language command parsing and help -//! - **Hook Analysis**: Smart filtering of shell events for notifications -//! -//! # Quick Start -//! -//! ```no_run -//! use baml::{BamlClient, BamlClientConfig}; -//! -//! #[tokio::main] -//! async fn main() -> Result<(), Box<dyn std::error::Error>> { -//! // Create client (assumes BAML server running on localhost:2024) -//! let client = BamlClient::default(); -//! -//! // Categorize a notification -//! let result = client.categorize_notification( -//! "Database Error", -//! "Connection to PostgreSQL failed", -//! "production monitoring" -//! ).await?; -//! -//! println!("Category: {}", result.category); -//! println!("Priority: {}", result.priority); -//! println!("Reasoning: {}", result.reasoning); -//! -//! Ok(()) -//! } -//! ``` -//! -//! # Configuration -//! -//! By default, the client connects to `http://localhost:2024`. You can customize this: -//! -//! ```no_run -//! use baml::{BamlClient, BamlClientConfig}; -//! -//! let config = BamlClientConfig::new("http://baml-server:8080") -//! .with_timeout(60) // 60 second timeout -//! .with_max_retries(3); // retry up to 3 times -//! -//! let client = BamlClient::new(config); -//! ``` -//! -//! # Starting the BAML Server -//! -//! Before using this client, you must start the BAML server: -//! -//! ```bash -//! # From the project root -//! baml serve -//! ``` -//! -//! The server will load all BAML function definitions from `baml_src/` and expose -//! them via a REST API. -//! -//! # Error Handling -//! -//! All client methods return `Result<T, BamlError>`. Common errors include: -//! -//! - `ServerUnreachable`: BAML server is not running or not accessible -//! - `FunctionNotFound`: BAML function doesn't exist (typo or version mismatch) -//! - `Timeout`: Request took longer than configured timeout -//! - `ServerError`: BAML server returned an error (check server logs) -//! -//! # Integration with Services -//! -//! ## agentd-notify -//! -//! ```no_run -//! # use baml::BamlClient; -//! # async fn example(client: BamlClient, title: &str, message: &str) -> Result<(), Box<dyn std::error::Error>> { -//! // Auto-categorize incoming notifications -//! let category = client.categorize_notification( -//! title, -//! message, -//! "notification-service" -//! ).await?; -//! -//! // Use the AI-suggested priority -//! let priority = category.priority; -//! # Ok(()) -//! # } -//! ``` -//! -//! ## agentd-ask -//! -//! ```no_run -//! # use baml::BamlClient; -//! # async fn example(client: BamlClient) -> Result<(), Box<dyn std::error::Error>> { -//! // Generate contextual questions -//! let question = client.generate_system_question( -//! "tmux_sessions", -//! "0 sessions running", -//! "User in terminal", -//! "Last session ended 2 hours ago" -//! ).await?; -//! -//! println!("Ask user: {}", question.question_text); -//! # Ok(()) -//! # } -//! ``` -//! -//! ## agentd-monitor -//! -//! ```no_run -//! # use baml::BamlClient; -//! # async fn example(client: BamlClient, logs: Vec<String>) -> Result<(), Box<dyn std::error::Error>> { -//! // Analyze service logs -//! let analysis = client.analyze_logs( -//! "agentd-notify", -//! &logs, -//! "last 5 minutes" -//! ).await?; -//! -//! if analysis.has_errors { -//! println!("Issues detected: {}", analysis.error_summary); -//! } -//! # Ok(()) -//! # } -//! ``` - -pub mod client; -pub mod error; -pub mod types; - -pub use client::{BamlClient, BamlClientConfig}; -pub use error::{BamlError, Result}; -pub use types::*; diff --git a/crates/baml/src/types.rs b/crates/baml/src/types.rs deleted file mode 100644 index 026edd21..00000000 --- a/crates/baml/src/types.rs +++ /dev/null @@ -1,456 +0,0 @@ -//! Type definitions matching BAML function inputs and outputs. -//! -//! These types correspond to the BAML class definitions in baml_src/. - -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; - -// ============================================================================ -// Notification Types (from notifications.baml) -// ============================================================================ - -/// Result of notification categorization -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct NotificationCategory { - /// Category type: "urgent", "info", "action_required", "reminder", "system", "error" - pub category: String, - /// Priority level: "low", "normal", "high", "urgent" - pub priority: String, - /// Suggested lifetime: "ephemeral" (auto-dismiss), "persistent" (keep until dismissed) - pub suggested_lifetime: String, - /// Brief explanation of the categorization decision - pub reasoning: String, - /// Recommended action for the user (if any) - pub suggested_action: Option<String>, -} - -/// Summary of multiple notifications for digest generation -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct NotificationDigest { - /// High-level summary of notification activity - pub summary: String, - /// Key actions that require user attention - pub key_actions: Vec<String>, - /// Count of notifications by priority - pub urgent_count: i32, - pub high_count: i32, - pub normal_count: i32, - pub low_count: i32, - /// Distribution of notifications by category - pub categories: HashMap<String, i32>, - /// Observed trends or patterns in notification activity - pub trends: String, - /// Actionable recommendations for the user - pub recommendations: Vec<String>, -} - -/// Notification grouping suggestion -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct NotificationGroup { - /// Group title/name - pub title: String, - /// IDs of notifications that should be grouped together - pub notification_ids: Vec<String>, - /// Reason for grouping - pub reasoning: String, - /// Suggested group action (e.g., "mark all as read", "dismiss group") - pub suggested_group_action: Option<String>, -} - -// ============================================================================ -// Question Types (from questions.baml) -// ============================================================================ - -/// Generated system question with context and suggestions -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SystemQuestion { - /// The question text to present to the user - pub question_text: String, - /// Suggested responses (e.g., ["yes", "no", "later"], or specific options) - pub suggested_responses: Vec<String>, - /// Brief explanation of why this question is being asked - pub reasoning: String, - /// Urgency level: "low", "normal", "high", "urgent" - pub urgency: String, - /// Follow-up actions the system might take based on user response - pub follow_up_actions: HashMap<String, String>, - /// Additional context or help text for the user - pub help_text: Option<String>, -} - -/// Analysis of user's previous answer to improve future questions -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct AnswerAnalysis { - /// Interpreted meaning of the user's answer - pub interpretation: String, - /// Confidence in interpretation (0.0 - 1.0) - pub confidence: f64, - /// Suggested system action based on this answer - pub suggested_action: String, - /// Whether a follow-up question might be needed - pub needs_followup: bool, - /// Optional follow-up question if needed - pub followup_question: Option<String>, -} - -/// Question effectiveness feedback for learning -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct QuestionFeedback { - /// Whether the question was clear and actionable - pub was_clear: bool, - /// Whether the user found the question relevant - pub was_relevant: bool, - /// Suggestions for improving this question type - pub improvement_suggestions: Vec<String>, - /// Optimal time to ask this question (if timing was an issue) - pub better_timing: Option<String>, -} - -// ============================================================================ -// Monitoring Types (from monitoring.baml) -// ============================================================================ - -/// Analysis results for log entries -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LogAnalysis { - /// Whether errors were detected in the logs - pub has_errors: bool, - /// High-level summary of error patterns or issues - pub error_summary: String, - /// List of services or components affected by issues - pub affected_services: Vec<String>, - /// Specific, actionable steps to resolve issues - pub suggested_actions: Vec<String>, - /// Severity level: "info", "warning", "error", "critical" - pub severity: String, - /// Root cause analysis (if determinable from logs) - pub root_cause: String, - /// Whether immediate action is required - pub requires_immediate_attention: bool, - /// Estimated impact on system operations - pub impact_assessment: String, -} - -/// Pattern detection in logs -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LogPattern { - /// Description of the detected pattern - pub pattern_description: String, - /// How many times this pattern appeared - pub occurrence_count: i32, - /// Time range when pattern was observed - pub time_range: String, - /// Whether this pattern indicates a problem - pub is_problematic: bool, - /// Confidence in pattern detection (0.0 - 1.0) - pub confidence: f64, - /// Suggested investigation steps - pub investigation_steps: Vec<String>, -} - -/// Health assessment of a service -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ServiceHealth { - /// Service name - pub service_name: String, - /// Overall health status: "healthy", "degraded", "unhealthy", "critical" - pub status: String, - /// Specific health indicators and their values - pub indicators: HashMap<String, String>, - /// Issues detected (if any) - pub issues: Vec<String>, - /// Recommended remediation actions - pub recommendations: Vec<String>, - /// Confidence in health assessment (0.0 - 1.0) - pub confidence: f64, - /// Predicted impact if issues not addressed - pub impact_if_unresolved: Option<String>, -} - -/// Performance anomaly detection -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PerformanceAnomaly { - /// Metric that shows anomalous behavior - pub metric_name: String, - /// Current value of the metric - pub current_value: String, - /// Expected/normal value range - pub expected_range: String, - /// Deviation from normal (percentage or description) - pub deviation: String, - /// Possible causes of the anomaly - pub possible_causes: Vec<String>, - /// Recommended investigation steps - pub investigation_steps: Vec<String>, - /// Severity of the anomaly - pub severity: String, -} - -// ============================================================================ -// CLI Types (from cli.baml) -// ============================================================================ - -/// Parsed command intent from natural language input -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CommandIntent { - /// The identified action - pub action: String, - /// Extracted parameters for the command - pub parameters: HashMap<String, String>, - /// Confidence in the intent parsing (0.0 - 1.0) - pub confidence: f64, - /// Whether the command should ask for confirmation before executing - pub requires_confirmation: bool, - /// Human-readable explanation of what will be executed - pub execution_summary: String, - /// Warning messages if the command might have unintended consequences - pub warnings: Option<Vec<String>>, -} - -/// Suggestion for command completion or correction -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CommandSuggestion { - /// Suggested command to run - pub suggested_command: String, - /// Explanation of what this command does - pub explanation: String, - /// Confidence that this is what the user intended - pub confidence: f64, - /// Alternative suggestions - pub alternatives: Option<Vec<String>>, -} - -/// Help information for natural language query -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct HelpResponse { - /// Direct answer to the user's question - pub answer: String, - /// Relevant examples showing how to accomplish the task - pub examples: Vec<String>, - /// Related commands or topics - pub related_topics: Vec<String>, - /// Whether this requires additional context from user - pub needs_more_info: bool, - /// Follow-up questions to clarify user's intent - pub followup_questions: Option<Vec<String>>, -} - -// ============================================================================ -// Hook Types (from hooks.baml) -// ============================================================================ - -/// Decision about whether and how to notify for a hook event -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct HookAction { - /// Whether a notification should be sent - pub should_notify: bool, - /// Notification title (if should_notify is true) - pub notification_title: String, - /// Notification message body (if should_notify is true) - pub notification_message: String, - /// Suggested priority: "low", "normal", "high", "urgent" - pub priority: String, - /// Brief explanation of the decision - pub reasoning: String, - /// Metadata to attach to the notification for context - pub metadata: HashMap<String, String>, - /// Whether this event indicates a problem - pub indicates_problem: bool, - /// Suggested actions for the user (if applicable) - pub suggested_actions: Option<Vec<String>>, -} - -/// Pattern learned from hook event history -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct HookPattern { - /// Description of the pattern - pub pattern_description: String, - /// Commands or events that match this pattern - pub matching_criteria: String, - /// Whether events matching this pattern should notify - pub should_notify: bool, - /// Suggested notification priority for this pattern - pub notification_priority: String, - /// How many times this pattern has been observed - pub occurrence_count: i32, - /// Confidence in this pattern (0.0 - 1.0) - pub confidence: f64, -} - -/// Intelligence about command execution -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CommandInsight { - /// What the command appears to be doing - pub purpose: String, - /// Whether this is a long-running command - pub is_long_running: bool, - /// Expected behavior (success indicators) - pub expected_outcomes: Vec<String>, - /// Potential issues to watch for - pub potential_issues: Vec<String>, - /// Whether user typically cares about this command's completion - pub user_cares_about_completion: bool, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_notification_category_serde() { - let category = NotificationCategory { - category: "error".to_string(), - priority: "high".to_string(), - suggested_lifetime: "persistent".to_string(), - reasoning: "Critical issue".to_string(), - suggested_action: Some("Investigate".to_string()), - }; - - let json = serde_json::to_string(&category).unwrap(); - let deserialized: NotificationCategory = serde_json::from_str(&json).unwrap(); - assert_eq!(deserialized.category, "error"); - assert_eq!(deserialized.priority, "high"); - assert_eq!(deserialized.suggested_action, Some("Investigate".to_string())); - } - - #[test] - fn test_notification_category_optional_field() { - let json = r#"{ - "category": "info", - "priority": "low", - "suggested_lifetime": "ephemeral", - "reasoning": "FYI", - "suggested_action": null - }"#; - let category: NotificationCategory = serde_json::from_str(json).unwrap(); - assert!(category.suggested_action.is_none()); - } - - #[test] - fn test_notification_digest_serde() { - let digest = NotificationDigest { - summary: "5 alerts".to_string(), - key_actions: vec!["Review".to_string()], - urgent_count: 1, - high_count: 2, - normal_count: 1, - low_count: 1, - categories: HashMap::from([("error".to_string(), 3)]), - trends: "Increasing".to_string(), - recommendations: vec!["Check logs".to_string()], - }; - - let json = serde_json::to_string(&digest).unwrap(); - let deserialized: NotificationDigest = serde_json::from_str(&json).unwrap(); - assert_eq!(deserialized.urgent_count, 1); - assert_eq!(deserialized.categories.get("error"), Some(&3)); - } - - #[test] - fn test_system_question_serde() { - let question = SystemQuestion { - question_text: "Start tmux?".to_string(), - suggested_responses: vec!["yes".to_string(), "no".to_string()], - reasoning: "No sessions running".to_string(), - urgency: "normal".to_string(), - follow_up_actions: HashMap::from([("yes".to_string(), "start session".to_string())]), - help_text: None, - }; - - let json = serde_json::to_string(&question).unwrap(); - let deserialized: SystemQuestion = serde_json::from_str(&json).unwrap(); - assert_eq!(deserialized.question_text, "Start tmux?"); - assert_eq!(deserialized.suggested_responses.len(), 2); - } - - #[test] - fn test_answer_analysis_serde() { - let analysis = AnswerAnalysis { - interpretation: "User agrees".to_string(), - confidence: 0.95, - suggested_action: "proceed".to_string(), - needs_followup: false, - followup_question: None, - }; - - let json = serde_json::to_string(&analysis).unwrap(); - let deserialized: AnswerAnalysis = serde_json::from_str(&json).unwrap(); - assert_eq!(deserialized.confidence, 0.95); - assert!(!deserialized.needs_followup); - } - - #[test] - fn test_log_analysis_serde() { - let analysis = LogAnalysis { - has_errors: true, - error_summary: "Connection timeouts".to_string(), - affected_services: vec!["db".to_string()], - suggested_actions: vec!["Restart".to_string()], - severity: "error".to_string(), - root_cause: "Network".to_string(), - requires_immediate_attention: true, - impact_assessment: "High".to_string(), - }; - - let json = serde_json::to_string(&analysis).unwrap(); - let deserialized: LogAnalysis = serde_json::from_str(&json).unwrap(); - assert!(deserialized.has_errors); - assert!(deserialized.requires_immediate_attention); - } - - #[test] - fn test_command_intent_serde() { - let intent = CommandIntent { - action: "list".to_string(), - parameters: HashMap::from([("status".to_string(), "pending".to_string())]), - confidence: 0.9, - requires_confirmation: false, - execution_summary: "List pending items".to_string(), - warnings: None, - }; - - let json = serde_json::to_string(&intent).unwrap(); - let deserialized: CommandIntent = serde_json::from_str(&json).unwrap(); - assert_eq!(deserialized.action, "list"); - assert_eq!(deserialized.confidence, 0.9); - } - - #[test] - fn test_hook_action_serde() { - let action = HookAction { - should_notify: true, - notification_title: "Build Failed".to_string(), - notification_message: "Error in main.rs".to_string(), - priority: "high".to_string(), - reasoning: "Build failure".to_string(), - metadata: HashMap::new(), - indicates_problem: true, - suggested_actions: Some(vec!["Fix errors".to_string()]), - }; - - let json = serde_json::to_string(&action).unwrap(); - let deserialized: HookAction = serde_json::from_str(&json).unwrap(); - assert!(deserialized.should_notify); - assert!(deserialized.indicates_problem); - } - - #[test] - fn test_baml_error_display() { - use crate::error::BamlError; - - let err = BamlError::FunctionNotFound { function_name: "TestFunc".to_string() }; - assert!(err.to_string().contains("TestFunc")); - - let err = BamlError::ServerError { status: 500, message: "fail".to_string() }; - assert!(err.to_string().contains("500")); - - let err = BamlError::Timeout { timeout_secs: 30 }; - assert!(err.to_string().contains("30")); - - let err = BamlError::InvalidResponse("bad json".to_string()); - assert!(err.to_string().contains("bad json")); - - let err = BamlError::ConfigError("bad config".to_string()); - assert!(err.to_string().contains("bad config")); - } -} diff --git a/crates/common/src/storage.rs b/crates/common/src/storage.rs index 8cf92e8a..00bb93c1 100644 --- a/crates/common/src/storage.rs +++ b/crates/common/src/storage.rs @@ -20,36 +20,11 @@ use directories::ProjectDirs; use sea_orm::{Database, DatabaseConnection}; use std::path::{Path, PathBuf}; -/// Resolve the platform-specific database file path for a service. -/// -/// Uses the XDG base directory specification (via `directories` crate) to -/// determine the data directory, creates it if necessary, and returns the -/// full path to the database file. -/// -/// # Arguments -/// -/// * `project_name` — XDG project qualifier (e.g., `"agentd-notify"`) -/// * `db_filename` — Database filename (e.g., `"notify.db"`) -/// -/// # Examples -/// -/// ```rust,ignore -/// let path = agentd_common::storage::get_db_path("agentd-notify", "notify.db")?; -/// // macOS: ~/Library/Application Support/agentd-notify/notify.db -/// // Linux: ~/.local/share/agentd-notify/notify.db -/// ``` /// Resolve the data directory for a project based on the `AGENTD_ENV` /// environment variable. /// -/// | `AGENTD_ENV` value | Resolved path | -/// |---|---| -/// | `development` or `dev` | `tmp/` | -/// | `test` | `tmp/test/` | -/// | *(absent or any other value)* | XDG data dir for `project_name` | -/// -/// Extracting this logic into its own function makes it independently -/// testable: callers can set `AGENTD_ENV` and verify the result without -/// triggering directory creation or database connection setup. +/// Returns `tmp/` in development mode, `tmp/test/` in test mode, or the +/// XDG platform data directory in production. Does not create any directories. fn resolve_data_dir(project_name: &str) -> Result<PathBuf> { match std::env::var("AGENTD_ENV").as_deref() { Ok("development" | "dev") => Ok(PathBuf::from("tmp")), @@ -63,6 +38,30 @@ fn resolve_data_dir(project_name: &str) -> Result<PathBuf> { } } +/// Resolve the platform-specific database file path for a service. +/// +/// Uses the `AGENTD_ENV` environment variable to select the data directory +/// (`development`/`dev` → `tmp/`, `test` → `tmp/test/`, absent → XDG platform +/// data dir), creates the directory if necessary, and returns the full path to +/// the database file. +/// +/// # Arguments +/// +/// * `project_name` — XDG project qualifier (e.g., `"agentd-notify"`) +/// * `db_filename` — Database filename (e.g., `"notify.db"`) +/// +/// # Errors +/// +/// Returns an error if the XDG project directories cannot be determined or +/// if `create_dir_all` fails. +/// +/// # Examples +/// +/// ```rust,ignore +/// let path = agentd_common::storage::get_db_path("agentd-notify", "notify.db")?; +/// // macOS: ~/Library/Application Support/agentd-notify/notify.db +/// // Linux: ~/.local/share/agentd-notify/notify.db +/// ``` pub fn get_db_path(project_name: &str, db_filename: &str) -> Result<PathBuf> { let data_dir = resolve_data_dir(project_name)?; std::fs::create_dir_all(&data_dir)?; @@ -147,31 +146,26 @@ mod tests { // environment. #[test] - fn test_resolve_data_dir_development() { + fn test_resolve_data_dir_respects_agentd_env() { + // Run all AGENTD_ENV variants sequentially inside one test so only + // one thread mutates the process environment at a time. + + // development std::env::set_var("AGENTD_ENV", "development"); let dir = resolve_data_dir("agentd-test-common").unwrap(); assert_eq!(dir, PathBuf::from("tmp")); - std::env::remove_var("AGENTD_ENV"); - } - #[test] - fn test_resolve_data_dir_dev_shorthand() { + // dev shorthand std::env::set_var("AGENTD_ENV", "dev"); let dir = resolve_data_dir("agentd-test-common").unwrap(); assert_eq!(dir, PathBuf::from("tmp")); - std::env::remove_var("AGENTD_ENV"); - } - #[test] - fn test_resolve_data_dir_test() { + // test std::env::set_var("AGENTD_ENV", "test"); let dir = resolve_data_dir("agentd-test-common").unwrap(); assert_eq!(dir, PathBuf::from("tmp/test")); - std::env::remove_var("AGENTD_ENV"); - } - #[test] - fn test_resolve_data_dir_production_contains_project_name() { + // production (no AGENTD_ENV set) — XDG path must contain the project name std::env::remove_var("AGENTD_ENV"); let dir = resolve_data_dir("agentd-test-common").unwrap(); assert!( diff --git a/ui/src/styles/themes.ts b/ui/src/styles/themes.ts deleted file mode 100644 index d598f638..00000000 --- a/ui/src/styles/themes.ts +++ /dev/null @@ -1,142 +0,0 @@ -/** - * themes.ts — theme color definitions. - * - * Exports: - * - `lightColors` / `darkColors`: full color maps for each theme - * - `lightNivoTheme` / `darkNivoTheme`: Nivo chart theme objects (PartialTheme) - * - * These can be used with the `useNivoTheme()` hook which returns the - * appropriate Nivo theme based on the active theme. - */ - -import type { PartialTheme as NivoTheme } from '@nivo/theming' - -// --------------------------------------------------------------------------- -// Color palettes -// --------------------------------------------------------------------------- - -export const lightColors = { - // Backgrounds - bgPrimary: '#f8fafc', // secondary-50 — page background - bgSecondary: '#f1f5f9', // secondary-100 — card background - bgTertiary: '#e2e8f0', // secondary-200 — subtle surface - - // Text - textPrimary: '#0f172a', // secondary-900 - textSecondary: '#334155', // secondary-700 - textMuted: '#64748b', // secondary-500 - - // Borders - borderDefault: '#e2e8f0', // secondary-200 - borderStrong: '#cbd5e1', // secondary-300 - - // Accent - accentPrimary: '#3b82f6', // primary-500 - accentSecondary: '#2563eb', // primary-600 - - // Status - success: '#22c55e', - warning: '#f59e0b', - error: '#ef4444', - info: '#06b6d4', -} as const - -export const darkColors = { - // Backgrounds - bgPrimary: '#020617', // secondary-950 — page background - bgSecondary: '#0f172a', // secondary-900 — card background - bgTertiary: '#1e293b', // secondary-800 — subtle surface - - // Text - textPrimary: '#f8fafc', // secondary-50 - textSecondary: '#cbd5e1', // secondary-300 - textMuted: '#94a3b8', // secondary-400 - - // Borders - borderDefault: '#1e293b', // secondary-800 - borderStrong: '#334155', // secondary-700 - - // Accent - accentPrimary: '#60a5fa', // primary-400 - accentSecondary: '#93c5fd', // primary-300 - - // Status - success: '#22c55e', - warning: '#f59e0b', - error: '#ef4444', - info: '#06b6d4', -} as const - -// --------------------------------------------------------------------------- -// Nivo chart themes -// --------------------------------------------------------------------------- - -export const lightNivoTheme: NivoTheme = { - background: lightColors.bgSecondary, - axis: { - domain: { - line: { stroke: lightColors.borderStrong, strokeWidth: 1 }, - }, - ticks: { - line: { stroke: lightColors.borderDefault, strokeWidth: 1 }, - text: { fill: lightColors.textMuted, fontSize: 11 }, - }, - legend: { - text: { fill: lightColors.textSecondary, fontSize: 12, fontWeight: 500 }, - }, - }, - grid: { - line: { stroke: lightColors.borderDefault, strokeWidth: 1 }, - }, - legends: { - text: { fill: lightColors.textSecondary, fontSize: 12 }, - }, - tooltip: { - container: { - background: '#ffffff', - color: lightColors.textPrimary, - fontSize: 12, - borderRadius: 6, - boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)', - border: `1px solid ${lightColors.borderDefault}`, - }, - }, - labels: { - text: { fill: lightColors.textSecondary, fontSize: 11 }, - }, -} - -export const darkNivoTheme: NivoTheme = { - background: darkColors.bgSecondary, - axis: { - domain: { - line: { stroke: darkColors.borderStrong, strokeWidth: 1 }, - }, - ticks: { - line: { stroke: darkColors.borderDefault, strokeWidth: 1 }, - text: { fill: darkColors.textMuted, fontSize: 11 }, - }, - legend: { - text: { fill: darkColors.textSecondary, fontSize: 12, fontWeight: 500 }, - }, - }, - grid: { - line: { stroke: darkColors.borderDefault, strokeWidth: 1 }, - }, - legends: { - text: { fill: darkColors.textSecondary, fontSize: 12 }, - }, - tooltip: { - container: { - background: darkColors.bgTertiary, - color: darkColors.textPrimary, - fontSize: 12, - borderRadius: 6, - boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.25)', - border: `1px solid ${darkColors.borderStrong}`, - }, - }, - labels: { - text: { fill: darkColors.textSecondary, fontSize: 11 }, - }, -} From d2229127d2f6e32e77c028ba056b88be91775c3b Mon Sep 17 00:00:00 2001 From: Geoff Johnson <geoff.jay@gmail.com> Date: Mon, 23 Mar 2026 09:27:06 -0700 Subject: [PATCH 4/4] refactor(common): extract resolve_data_dir helper from get_db_path --- Cargo.toml | 1 - crates/hook/Cargo.toml | 5 ----- crates/monitor/Cargo.toml | 5 ----- 3 files changed, 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7c644dd9..540d6130 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,6 @@ resolver = "2" members = [ "crates/ask", - "crates/baml", "crates/cli", "crates/common", "crates/communicate", diff --git a/crates/hook/Cargo.toml b/crates/hook/Cargo.toml index 05d779e0..05efed44 100644 --- a/crates/hook/Cargo.toml +++ b/crates/hook/Cargo.toml @@ -27,11 +27,6 @@ chrono = { workspace = true } uuid = { version = "1.0", features = ["serde", "v4"] } reqwest = { workspace = true, features = ["json"] } agentd-common = { path = "../common" } -baml = { path = "../baml", optional = true } - -[features] -baml-analysis = ["baml"] - [dev-dependencies] tower = { version = "0.5", features = ["util"] } http-body-util = "0.1" diff --git a/crates/monitor/Cargo.toml b/crates/monitor/Cargo.toml index d77e2d3b..2a9e194b 100644 --- a/crates/monitor/Cargo.toml +++ b/crates/monitor/Cargo.toml @@ -30,11 +30,6 @@ reqwest = { workspace = true, features = ["json"] } agentd-common = { path = "../common" } metrics = { workspace = true } metrics-exporter-prometheus = { workspace = true } -baml = { path = "../baml", optional = true } - -[features] -baml-integration = ["baml"] - [dev-dependencies] tower = { version = "0.5", features = ["util"] } http-body-util = "0.1"