feat(workspace): add TOOLS.md, BOOTSTRAP.md, and disk-to-DB import#477
feat(workspace): add TOOLS.md, BOOTSTRAP.md, and disk-to-DB import#477henrypark133 merged 2 commits intomainfrom
Conversation
Add two new OpenClaw-compatible workspace markdown files: - TOOLS.md: Environment-specific tool notes (SSH hosts, device names, etc.) injected into the system prompt under "## Tool Notes". Seeded as comment-only (like HEARTBEAT.md) so it's effectively empty until the user adds real content. Not write-protected — the agent can update it as it learns the environment. - BOOTSTRAP.md: First-run onboarding ritual. Injected FIRST in the system prompt when present. Guides the agent through introducing itself, learning about the user, and updating workspace files. Only seeded on truly fresh workspaces (no existing identity files) to avoid triggering the ritual on existing deployments. Agent clears it via `memory_write(target="bootstrap")` when done. Add `Workspace::import_from_directory()` for disk-to-DB import: - Scans a directory for *.md files and imports any that don't already exist in the database (never overwrites user edits) - Controlled by WORKSPACE_IMPORT_DIR env var, runs after seed_if_empty() - Enables Docker images / deployment scripts to ship customized workspace templates that override generic seeds - Backwards compatible: no-op when env var is unset Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the configurability and initial setup experience for agents by introducing new workspace files and a mechanism for importing external configurations. It provides dedicated files for environment-specific tool notes and a structured first-run onboarding process, allowing for more tailored and efficient agent initialization and operation. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces several useful features for workspace management, including TOOLS.md for tool notes, BOOTSTRAP.md for first-run onboarding, and a disk-to-database import mechanism. The implementation is generally solid and well-documented.
My review focuses on improving the robustness of the new import feature and a small performance optimization during workspace seeding. I've also pointed out the use of an unstable Rust feature that could be replaced with a stable equivalent to ensure broader compatibility.
Note: Security Review is unavailable for this PR.
|
|
||
| let path = entry.path(); | ||
| // Only import .md files | ||
| if path.extension().is_none_or(|ext| ext != "md") { |
There was a problem hiding this comment.
The method is_none_or is currently an unstable feature, available only on nightly Rust. To ensure compatibility with stable Rust, it's better to use the equivalent stable expression path.extension() != Some(std::ffi::OsStr::new("md")). This achieves the same logic of filtering for files that do not have a .md extension.
if path.extension() != Some(std::ffi::OsStr::new("md")) {There was a problem hiding this comment.
Fixed in eb270dd — switched to stable path.extension() != Some(OsStr::new("md")).
| let is_fresh_workspace = matches!( | ||
| self.read(paths::AGENTS).await, | ||
| Err(WorkspaceError::DocumentNotFound { .. }) | ||
| ) && matches!( | ||
| self.read(paths::SOUL).await, | ||
| Err(WorkspaceError::DocumentNotFound { .. }) | ||
| ) && matches!( | ||
| self.read(paths::USER).await, | ||
| Err(WorkspaceError::DocumentNotFound { .. }) | ||
| ); |
There was a problem hiding this comment.
To improve startup performance, these three database reads can be executed concurrently using tokio::join!. This will reduce latency, especially when the database has higher network latency.
| let is_fresh_workspace = matches!( | |
| self.read(paths::AGENTS).await, | |
| Err(WorkspaceError::DocumentNotFound { .. }) | |
| ) && matches!( | |
| self.read(paths::SOUL).await, | |
| Err(WorkspaceError::DocumentNotFound { .. }) | |
| ) && matches!( | |
| self.read(paths::USER).await, | |
| Err(WorkspaceError::DocumentNotFound { .. }) | |
| ); | |
| let (agents_res, soul_res, user_res) = tokio::join!( | |
| self.read(paths::AGENTS), | |
| self.read(paths::SOUL), | |
| self.read(paths::USER) | |
| ); | |
| let is_fresh_workspace = matches!(agents_res, Err(WorkspaceError::DocumentNotFound { .. })) | |
| && matches!(soul_res, Err(WorkspaceError::DocumentNotFound { .. })) | |
| && matches!(user_res, Err(WorkspaceError::DocumentNotFound { .. })); |
There was a problem hiding this comment.
Fixed in eb270dd — using tokio::join! for the three reads now.
| let entry = entry.map_err(|e| WorkspaceError::IoError { | ||
| reason: format!("failed to read directory entry: {}", e), | ||
| })?; |
There was a problem hiding this comment.
The current implementation will cause the entire import process to fail if a single directory entry is unreadable (e.g., due to permissions). It would be more robust to log a warning and skip the problematic entry, allowing other valid files to be imported. The calling code in app.rs already treats import failures as non-fatal warnings, so continuing on individual entry errors aligns with that design.
let entry = match entry {
Ok(e) => e,
Err(e) => {
tracing::warn!("Failed to read directory entry in {}: {}", dir.display(), e);
continue;
}
};There was a problem hiding this comment.
Fixed in eb270dd — now logs a warning and continues instead of failing the whole import.
| let content = std::fs::read_to_string(&path).map_err(|e| WorkspaceError::IoError { | ||
| reason: format!("failed to read {}: {}", path.display(), e), | ||
| })?; |
There was a problem hiding this comment.
Similar to the directory entry reading, if a file is found but cannot be read (e.g., due to permissions), the entire import process will fail. It would be more robust to log this as a warning and continue with the next file.
let content = match std::fs::read_to_string(&path) {
Ok(c) => c,
Err(e) => {
tracing::warn!("Failed to read import file {}: {}", path.display(), e);
continue;
}
};There was a problem hiding this comment.
Fixed in eb270dd — same treatment, warns and skips unreadable files.
- Use stable `path.extension() != Some(OsStr::new("md"))` instead of
unstable `is_none_or` (nightly-only)
- Use `tokio::join!` for concurrent DB reads in fresh-workspace check
- Skip unreadable directory entries instead of failing the entire import
- Skip unreadable files instead of failing the entire import
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…earai#477) * feat(workspace): add TOOLS.md, BOOTSTRAP.md, and disk-to-DB import Add two new OpenClaw-compatible workspace markdown files: - TOOLS.md: Environment-specific tool notes (SSH hosts, device names, etc.) injected into the system prompt under "## Tool Notes". Seeded as comment-only (like HEARTBEAT.md) so it's effectively empty until the user adds real content. Not write-protected — the agent can update it as it learns the environment. - BOOTSTRAP.md: First-run onboarding ritual. Injected FIRST in the system prompt when present. Guides the agent through introducing itself, learning about the user, and updating workspace files. Only seeded on truly fresh workspaces (no existing identity files) to avoid triggering the ritual on existing deployments. Agent clears it via `memory_write(target="bootstrap")` when done. Add `Workspace::import_from_directory()` for disk-to-DB import: - Scans a directory for *.md files and imports any that don't already exist in the database (never overwrites user edits) - Controlled by WORKSPACE_IMPORT_DIR env var, runs after seed_if_empty() - Enables Docker images / deployment scripts to ship customized workspace templates that override generic seeds - Backwards compatible: no-op when env var is unset Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review comments - Use stable `path.extension() != Some(OsStr::new("md"))` instead of unstable `is_none_or` (nightly-only) - Use `tokio::join!` for concurrent DB reads in fresh-workspace check - Skip unreadable directory entries instead of failing the entire import - Skip unreadable files instead of failing the entire import Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
TOOLS.md: New workspace file for environment-specific tool notes (SSH hosts, device names, etc.). Injected into the system prompt under
## Tool Notes. Seeded as comment-only so it's effectively empty until the user adds content. Agent-writable (not inPROTECTED_IDENTITY_FILES).BOOTSTRAP.md: First-run onboarding ritual file. Injected first in the system prompt when present. Guides the agent through introductions, learning about the user, and updating workspace files. Only seeded on fresh workspaces (no existing identity files) for backwards compatibility. Agent clears it via
memory_write(target="bootstrap")when done.Workspace::import_from_directory(): Disk-to-DB import controlled byWORKSPACE_IMPORT_DIRenv var. Scans a directory for*.mdfiles and imports any that don't already exist in the database. Enables Docker images / deployment scripts to ship customized workspace templates. Fully backwards compatible — no-op when env var is unset.Files changed
src/workspace/document.rs—paths::TOOLSandpaths::BOOTSTRAPconstantssrc/workspace/mod.rs— Seed content, system prompt injection,import_from_directory()src/app.rs— Wire upWORKSPACE_IMPORT_DIRimport afterseed_if_empty()src/error.rs—WorkspaceError::IoErrorvariantsrc/tools/builtin/memory.rs—"bootstrap"target inMemoryWriteToolsrc/workspace/README.md— Updated filesystem diagramSystem prompt order (after changes)
Backwards compatibility
WORKSPACE_IMPORT_DIR: no-op when unset; only imports files missing from DBseed_if_empty(): never overwrites existing files (unchanged behavior)Test plan
cargo clippy --all --benches --tests --examples --all-features— zero warningscargo check --no-default-features --features libsql— cleancargo test workspace— 10/10 pass.unwrap()or.expect()in production codeWORKSPACE_IMPORT_DIRimports disk files into DB🤖 Generated with Claude Code