--- description: YAMS-first agent with blackboard coordination and persistent memory argument-hint: [TASK=] [PBI=] [PHASE=] --- # Agent Workflow (YAMS + Blackboard) YAMS is the single source of truth for agent memory. When multiple agents are involved, use the blackboard tools to coordinate work. ## Core Identity - Default mode: use YAMS for memory (code + notes + decisions + research). - Coordination mode: always try to register on the blackboard and use it to share findings/tasks. - If blackboard tools are unavailable, fall back to the YAMS-only workflow (file claiming + metadata). ## Agent ID Convention - Use a stable, readable ID: `opencode-`. - Keep `` lowercase ASCII with dashes (example: `opencode-index-speedup`). ## Do / Don't ### Do - Always register on the blackboard at the start of a task. - Use the blackboard to coordinate: post findings, create/claim tasks, group work in contexts. - Search before acting: `yams grep` for code patterns; `yams search` for semantic/concept queries. - Index as you learn: add code, notes, decisions, and research to YAMS as you go. - Use metadata consistently so knowledge is queryable across sessions. ### Don't - Never `git push` without first indexing the work in YAMS. - Never delete files without first indexing the work in YAMS. - Don't duplicate the skill docs in this file; keep this file focused on behavior and workflow. ## Safety & Permissions ### Allowed Without Asking - Read/list files, run targeted tests/lints, run builds when explicitly requested. - YAMS operations: add/search/grep/graph/session/watch/status/doctor. - Blackboard operations: post/query/search/claim/update/complete findings and tasks. ### Ask First (Always) - `git push` (must index in YAMS first) - Deleting files (must index in YAMS first) - Installing new dependencies ## Required Metadata (Memory + PBI Tracking) Attach metadata to every `yams add`. - `task` - short task slug (example: `index-speedup`) - `phase` - `start` | `checkpoint` | `complete` - `owner` - `opencode` (shared owner for multi-agent retrieval) - `source` - `code` | `note` | `decision` | `research` Optional (when applicable): - `pbi` - PBI identifier (example: `PBI-043`) - `agent_id` - your canonical agent ID (example: `opencode-index-speedup`) ## Project Structure (Where To Look First) - CLI entry points and command wiring: `src/cli/` - Service layer and command handlers: `src/app/services/` - Search implementations (grep/semantic): `src/search/` - Storage engines and backends: `src/storage/` - Vector DB + embeddings: `src/vector/` - Daemon client/server: `src/daemon/` - MCP server implementation: `src/mcp/` ## Workflow ### 0) Register (Always) Try blackboard registration first. If it fails, continue with YAMS-only flow. ```text bb_register_agent({ id: "opencode-", name: "OpenCode Agent", capabilities: ["yams", "code", "coordination"] }) ``` ### Blackboard Coordination (Minimal) Keep coordination lightweight: findings capture what was discovered; tasks capture what needs doing. ```text bb_search_findings({ query: "" }) bb_post_finding({ agent_id: "opencode-", topic: "other", title: "", content: "<markdown>" }) bb_create_task({ title: "<work item>", type: "fix", priority: 2, created_by: "opencode-<task-slug>" }) bb_claim_task({ task_id: "<task-id>", agent_id: "opencode-<task-slug>" }) bb_update_task({ task_id: "<task-id>", status: "working" }) ``` ### 1) Search Existing Knowledge ```bash # Code patterns first yams grep "<pattern>" --cwd . # Semantic/concept search yams search "$TASK" --limit 20 # Structured metadata lookups yams search "task=$TASK" --type keyword --limit 20 ``` ### 1.5) YAMS-First Retrieval Behavior (Control-Loop Friendly) Use this order whenever you need content for reasoning: 1. `yams search`/`yams grep` to discover candidates 2. `yams get` (or MCP `get`) to read selected artifacts 3. Only then use local file reads if needed for implementation detail Behavior rules: - Prefer YAMS retrieval over ad-hoc local search/cat for knowledge lookup. - Do not mark chunks as "rejected" unless explicitly provided by user/model feedback. - Treat `served - used` as `not_used` (low-confidence negative), not hard rejection. Episode signal policy (implicit feedback): - High-confidence positive episode: `search -> get/read -> no immediate reformulation search`. - Negative signal: rapid reformulation loops, repeated failed searches, or abandon without `get/read`. - These are weak-supervision signals for tuning, not ground truth. Structured reporting (when retrieval is used): - Include `UsedContext: <chunk_ids or hashes>` when known. - Include `Citations: <artifact names/hashes/paths>`; if none, write `Citations: none`. - Preserve `trace_id` in logs/output paths when available for later attribution. ### 2) Start Work (Index Baseline + Claim) ```bash # Index baseline yams add . --recursive \ --include "*.cpp,*.hpp,*.h,*.py,*.ts,*.js,*.md" \ --label "Working on: $TASK" \ --metadata "pbi=$PBI,task=$TASK,phase=start,owner=opencode,source=code,agent_id=opencode-$TASK" ``` If blackboard is available, claim or create a task there. If not, "claim" files via YAMS metadata: ```bash yams add - --name "claim-$TASK.md" \ --metadata "pbi=$PBI,task=$TASK,phase=start,owner=opencode,source=note,agent_id=opencode-$TASK" \ <<'EOF' ## Claim Agent: opencode-$TASK Scope: <paths or subsystems> Goal: <one sentence> EOF ``` ### 3) Checkpoint (Index What Changed) ```bash yams add <changed-files> \ --label "$TASK: checkpoint" \ --metadata "pbi=$PBI,task=$TASK,phase=checkpoint,owner=opencode,source=code,agent_id=opencode-$TASK" ``` ### 4) Complete (Index + Close Loop) ```bash yams add . --recursive \ --include "*.cpp,*.hpp,*.h,*.py,*.ts,*.js,*.md" \ --label "Completed: $TASK" \ --metadata "pbi=$PBI,task=$TASK,phase=complete,owner=opencode,source=code,agent_id=opencode-$TASK" ``` ## Response Template ```text TASK: $TASK PBI: $PBI PHASE: $PHASE AGENT: opencode-$TASK CONTEXT FOUND: - Blackboard: <notes/findings/tasks> - YAMS: <docs/paths> ACTIONS: - <what changed and why> INDEXED: - <files/notes indexed> - Metadata: task=$TASK,phase=$PHASE,owner=opencode,agent_id=opencode-$TASK NEXT: - <next step> USED_CONTEXT: - <chunk_ids/hashes if known; else "unknown"> CITATIONS: - <artifact names/hashes/paths or "none"> ``` ## PR Checklist - All modified/new files indexed in YAMS with metadata - Any coordinated work reflected in blackboard tasks/findings (if available) - No secret material added to the repo or indexed notes - Commit/PR message explains "why" (not just "what") ## When Stuck - Ask one targeted clarifying question, with a recommended default. - Post a blackboard finding if the answer should help other agents. - Prefer small, reversible changes; avoid speculative rewrites. ## Context Recovery If the chat context is compacted or lost, rebuild it from YAMS using `yams list` filtered by `owner=opencode`, `task`, and recent time. ## References (Skills) - `yams` (YAMS memory + search + graph) - `yams-blackboard` (blackboard coordination on top of YAMS) --- ## C++ Style Guide (YAMS Patterns) `.clang-format` handles mechanical formatting (LLVM base, 4-space indent, 100-col limit, Attach braces, `c++20`). This section covers **project-specific patterns** that clang-format cannot enforce. ### Formatting Notes - Include order (enforced by `.clang-format` priorities): 1. `"yams/..."` project headers 2. `<tracy/...>` profiler 3. `<sqlite3...>` / `<sqlite_vec...>` 4. Third-party: `<spdlog/...>`, `<nlohmann/...>`, `<CLI11/...>`, `<magic_enum/...>`, `<toml/...>`, `<onnxruntime/...>` 5. Standard library `<algorithm>`, `<string>`, etc. - Pointer/reference alignment: left (`int* p`, `const std::string& s`). - Requires clauses go on their own line. ### Naming Conventions | Element | Style | Example | |---------|-------|---------| | Functions, variables | `camelCase` | `loadConfig()`, `chunkSize` | | Types (class/struct/enum/concept) | `PascalCase` | `ResourceSnapshot`, `ErrorCode` | | Compile-time constants | `kPascalCase` | `kBase`, `kNumPriorities`, `kStartupGracePeriod` | | Member variables | trailing `_` | `running_`, `strand_`, `autoEmbedPolicy_` | | Static inline atomics | trailing `_` | `postExtractionConcurrentDynamicCap_` | | Namespaces | `lowercase` | `yams::features`, `yams::config` | | Files | `snake_case` | `dispatch_utils.hpp`, `indexing_service.cpp` | | Test files | `*_catch2_test.cpp` | `tuning_reconciliation_catch2_test.cpp` | ### Error Handling: `Result<T>` Defined in `include/yams/core/types.h`. Variant-based (`std::variant<T, Error>`). ```cpp // Returning errors Result<Config> loadConfig(const std::string& path) { if (!exists(path)) return Error{ErrorCode::FileNotFound, "Config not found: " + path}; // ... return Config{...}; } // Consuming results auto result = loadConfig(path); if (!result) // explicit operator bool return result.error(); // propagate auto& cfg = result.value(); // access (throws if error) // Result<void> for side-effect operations Result<void> saveConfig(const Config& cfg); ``` **When to use what:** | Mechanism | Use for | |-----------|---------| | `Result<T>` | Fallible operations where the caller must handle errors | | `std::optional<T>` | "Absent" values with no error detail (lookups, parsing) | | Exceptions | Truly unrecoverable / programming errors (`std::runtime_error`) | Future: when `YAMS_HAS_EXPECTED` is available on all targets, `Result<T>` may migrate to `std::expected<T, Error>` (APIs are similar). ### Feature Gates (`YAMS_HAS_*`) Defined in `include/yams/core/cpp23_features.hpp`. Always use these macros, never raw `__cplusplus` checks. **Available macros:** | Macro | Feature | Minimum | |-------|---------|---------| | `YAMS_HAS_CONSTEXPR_VECTOR` | `constexpr std::vector` | GCC 13, Clang 16 | | `YAMS_HAS_CONSTEXPR_STRING` | `constexpr std::string` | GCC 13, Clang 16 | | `YAMS_HAS_CONSTEXPR_CONTAINERS` | Both of the above | GCC 13, Clang 16 | | `YAMS_HAS_EXPECTED` | `std::expected` | GCC 12, Clang 16 | | `YAMS_HAS_RANGES` | `std::ranges` / `std::views` | GCC 12, Clang 15 | | `YAMS_HAS_FLAT_MAP` | `std::flat_map` | GCC 12, Clang 17 | | `YAMS_HAS_MOVE_ONLY_FUNCTION` | `std::move_only_function` | GCC 12, Clang 17 | | `YAMS_HAS_STRING_CONTAINS` | `std::string::contains()` | GCC 12, Clang 12 | | `YAMS_HAS_LIKELY_UNLIKELY` | `[[likely]]` / `[[unlikely]]` | GCC 7, Clang 9 | | `YAMS_HAS_PROFILES` | C++26 Profiles (experimental) | -- | | `YAMS_HAS_REFLECTION` | C++26 Reflection (experimental) | -- | **Pattern:** ```cpp #include <yams/core/cpp23_features.hpp> #if YAMS_HAS_RANGES #include <ranges> auto filtered = items | std::views::filter(pred); #else std::vector<Item> filtered; std::copy_if(items.begin(), items.end(), std::back_inserter(filtered), pred); #endif ``` **Convenience macro:** `YAMS_CONSTEXPR_IF_SUPPORTED` expands to `constexpr` (C++23) or `inline` (C++20). **Compatibility helpers** in `yams::features::`: `string_contains()`, `string_starts_with()`, `string_ends_with()` -- work on both C++20 and C++23. **Adding a new gate:** 1. Add a `#ifndef` / `#if defined(__cpp_lib_xxx)` / `#define` block in `cpp23_features.hpp`. 2. Add it to the `FeatureInfo` struct. 3. Provide a C++20 fallback everywhere the feature is used. 4. Test with both C++20 and C++23 build configs. ### TuneAdvisor Knob Pattern Defined in `include/yams/daemon/components/TuneAdvisor.h` (~2800 lines). Central registry for all runtime-tunable parameters. **Static inline atomic triplet (override -> env -> default):** ```cpp // 1. Storage (private) static inline std::atomic<uint32_t> chunkSizeOverride_{0}; // 2. Getter (priority: override -> env -> computed default) static uint32_t chunkSize() { uint32_t ovr = chunkSizeOverride_.load(std::memory_order_relaxed); if (ovr) return ovr; if (const char* s = std::getenv("YAMS_CHUNK_SIZE")) { try { return static_cast<uint32_t>(std::stoull(s)); } catch (...) {} } return 512u * 1024u; // default: 512 KiB } // 3. Setter static void setChunkSize(uint32_t v) { chunkSizeOverride_.store(v, std::memory_order_relaxed); } ``` **`memory_order_relaxed`** is correct here because these knobs are best-effort advisory values read on hot paths; sequential consistency is not required. **DynamicCap vs Override (ratchet-down fix):** The tuning system has two write layers for post-ingest concurrency: | Layer | Written by | Purpose | |-------|-----------|---------| | `*Override_` | User / env | Hard cap, base budget input | | `*DynamicCap_` | `TuningManager::tick_once()` | Temporary scaling cap, can recover to base | `postIngestBudgetedConcurrency(bool includeDynamicCaps)`: - `false` -- returns base budget (for `ResourceGovernor::updateScalingCaps()`) - `true` -- returns effective value clamped by DynamicCaps (for runtime consumers: `PostIngestQueue`, `EmbeddingService`) Setting a DynamicCap to 0 clears it (effective value returns to base budget). ### InternalEventBus Defined in `include/yams/daemon/components/InternalEventBus.h`. Singleton with typed SPSC channels. ```cpp // Create or get a typed channel auto channel = InternalEventBus::instance() .get_or_create_channel<InternalEventBus::PostIngestTask>( "post_ingest_tasks", 4096); // Push (non-blocking) channel->try_push(InternalEventBus::PostIngestTask{hash, mimeType}); // Pop (non-blocking, returns std::optional or bool) InternalEventBus::PostIngestTask task; if (channel->try_pop(task)) { /* process */ } ``` **Job types** are nested structs: `EmbedJob`, `PostIngestTask`, `KgJob`, `StoreDocumentTask`, etc. Channels are keyed by string name and cached in a `std::unordered_map<std::string, std::shared_ptr<void>>`. Template type is recovered via `static_pointer_cast`. ### ResourceGovernor Interaction Singleton in `include/yams/daemon/components/ResourceGovernor.h`. Components should check admission before resource-intensive work. ```cpp auto& gov = ResourceGovernor::instance(); // Admission checks (call before starting expensive work) if (!gov.canAdmitWork()) return; // backpressure if (!gov.canScaleUp("embed", 2)) return; // would exceed budget if (!gov.canLoadModel(modelBytes)) return; // OOM risk // Query current scaling limits uint32_t maxWorkers = gov.maxIngestWorkers(); uint32_t maxEmbed = gov.maxEmbedConcurrency(); ``` **Pressure levels** (`ResourcePressureLevel`): `Normal` -> `Warning` -> `Critical` -> `Emergency`. The governor tracks consecutive ticks at each level and progressively reduces scaling headroom. **`ResourceSnapshot`** captures a point-in-time view of RSS, CPU, model memory, worker counts, and the computed pressure level. ### Profiling (`profiling.h`) All macros are no-ops when `TRACY_ENABLE` is not defined. Zero runtime cost in release builds. ```cpp #include <yams/profiling.h> void processChunk(ByteSpan data) { YAMS_ZONE_SCOPED_N("processChunk"); // named zone YAMS_PLOT("ChunkSize", data.size()); // numeric telemetry // ... } ``` **Domain-specific macros:** | Macro | Use in | |-------|--------| | `YAMS_STORAGE_ZONE(op)` | Storage engine operations (blue) | | `YAMS_ONNX_ZONE(op)` | ONNX inference calls (green) | | `YAMS_EMBEDDING_ZONE_BATCH(size)` | Embedding batch processing | | `YAMS_VECTOR_SEARCH_ZONE(k, threshold)` | Vector search operations | | `YAMS_SET_THREAD_NAME(name)` | Thread pool workers | ### Async Patterns The daemon uses `boost::asio` coroutines (C++20 coroutines with Boost wrappers). **Coroutine signature:** ```cpp boost::asio::awaitable<void> tuningLoop(); boost::asio::awaitable<Response> process(const Request& request); boost::asio::awaitable<bool> co_openDatabase(const fs::path& dbPath, int timeout_ms, yams::compat::stop_token token); ``` **Launching:** ```cpp // co_spawn with future (joinable) tuningFuture_ = boost::asio::co_spawn(strand_, tuningLoop(), boost::asio::use_future); // co_spawn detached (fire-and-forget) boost::asio::co_spawn(ioContext_, [&]() -> boost::asio::awaitable<void> { co_await someAsyncOp(); }, boost::asio::detached); ``` **Timed loop pattern:** ```cpp boost::asio::awaitable<void> TuningManager::tuningLoop() { boost::asio::steady_timer timer(co_await boost::asio::this_coro::executor); while (running_) { timer.expires_after(std::chrono::seconds(1)); co_await timer.async_wait(boost::asio::use_awaitable); tick_once(); } } ``` ### Testing Patterns Framework: **Catch2**. Test files live in `tests/unit/` and are named `*_catch2_test.cpp`. **RAII guards for global state:** TuneAdvisor uses `static inline` atomics, so tests must save/restore state to avoid cross-test contamination. ```cpp // Saves/restores TuneAdvisor::tuningProfile() across scope class ProfileGuard { public: explicit ProfileGuard(TuneAdvisor::Profile p) : prev_(TuneAdvisor::tuningProfile()) { TuneAdvisor::setTuningProfile(p); } ~ProfileGuard() { TuneAdvisor::setTuningProfile(prev_); } ProfileGuard(const ProfileGuard&) = delete; ProfileGuard& operator=(const ProfileGuard&) = delete; private: TuneAdvisor::Profile prev_; }; // Saves/restores an environment variable class EnvGuard { public: EnvGuard(const char* name, const char* value); ~EnvGuard(); // restores or unsets // non-copyable }; // Saves/restores TuneAdvisor::hardwareConcurrency() class HwGuard { /* similar pattern */ }; ``` **Usage:** ```cpp TEST_CASE("Budget scales with profile", "[tuning]") { EnvGuard envClean("YAMS_POST_INGEST_TOTAL_CONCURRENT", "0"); HwGuard hw(16); ProfileGuard profile(TuneAdvisor::Profile::Balanced); auto budget = TuneAdvisor::postIngestBudgetedConcurrency(false); REQUIRE(budget.extraction >= 1); REQUIRE(budget.total() <= 16); } ``` **Clearing atomics between tests:** Set overrides to 0 (or use guards) before each `TEST_CASE` to ensure a clean slate. DynamicCap atomics reset to 0 = "no cap".