Skip to content

feat(workspace): add TOOLS.md, BOOTSTRAP.md, and disk-to-DB import#477

Merged
henrypark133 merged 2 commits intomainfrom
feat/workspace-markdown-support
Mar 3, 2026
Merged

feat(workspace): add TOOLS.md, BOOTSTRAP.md, and disk-to-DB import#477
henrypark133 merged 2 commits intomainfrom
feat/workspace-markdown-support

Conversation

@henrypark133
Copy link
Copy Markdown
Collaborator

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 in PROTECTED_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 by WORKSPACE_IMPORT_DIR env var. Scans a directory for *.md files 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.rspaths::TOOLS and paths::BOOTSTRAP constants
  • src/workspace/mod.rs — Seed content, system prompt injection, import_from_directory()
  • src/app.rs — Wire up WORKSPACE_IMPORT_DIR import after seed_if_empty()
  • src/error.rsWorkspaceError::IoError variant
  • src/tools/builtin/memory.rs"bootstrap" target in MemoryWriteTool
  • src/workspace/README.md — Updated filesystem diagram

System prompt order (after changes)

1. BOOTSTRAP.md (first-run only, when present and non-empty)
2. AGENTS.md → SOUL.md → USER.md → IDENTITY.md (unchanged)
3. TOOLS.md (when non-empty)
4. MEMORY.md (non-group chat only, unchanged)
5. Daily logs (unchanged)

Backwards compatibility

  • Existing workspaces: BOOTSTRAP.md is NOT seeded if any identity files already exist
  • WORKSPACE_IMPORT_DIR: no-op when unset; only imports files missing from DB
  • seed_if_empty(): never overwrites existing files (unchanged behavior)
  • No schema changes, no new DB tables

Test plan

  • cargo clippy --all --benches --tests --examples --all-features — zero warnings
  • cargo check --no-default-features --features libsql — clean
  • cargo test workspace — 10/10 pass
  • No .unwrap() or .expect() in production code
  • Manual test: fresh workspace seeds TOOLS.md + BOOTSTRAP.md
  • Manual test: existing workspace does NOT get BOOTSTRAP.md
  • Manual test: WORKSPACE_IMPORT_DIR imports disk files into DB
  • Manual test: ironclaw-worker with WORKSPACE_IMPORT_DIR set

🤖 Generated with Claude Code

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>
@github-actions github-actions Bot added scope: tool/builtin Built-in tools scope: workspace Persistent memory / workspace scope: docs Documentation size: L 200-499 changed lines risk: medium Business logic, config, or moderate-risk modules contributor: core 20+ merged PRs labels Mar 2, 2026
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, 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

  • New Workspace Files: Introduced TOOLS.md for environment-specific tool notes and BOOTSTRAP.md for first-run agent onboarding, both integrated into the system prompt.
  • Disk-to-DB Import: Implemented Workspace::import_from_directory() allowing *.md files from a specified disk directory to be imported into the workspace database, enabling customized workspace templates.
  • Agent Onboarding Flow: BOOTSTRAP.md guides the agent through initial setup, is only seeded on fresh workspaces, and can be cleared by the agent using a new memory_write target.
Changelog
  • src/app.rs
    • Added logic to AppBuilder to import workspace files from a directory specified by the WORKSPACE_IMPORT_DIR environment variable after initial seeding.
  • src/error.rs
    • Added a new IoError variant to WorkspaceError to handle I/O related issues.
  • src/tools/builtin/memory.rs
    • Updated the MemoryWriteTool to support a new bootstrap target, allowing agents to clear the BOOTSTRAP.md file upon completion of the onboarding ritual.
    • Modified tool descriptions to reflect the new bootstrap target.
  • src/workspace/README.md
    • Updated the workspace filesystem diagram to include TOOLS.md and BOOTSTRAP.md.
  • src/workspace/document.rs
    • Defined new constants TOOLS and BOOTSTRAP for the paths of the new workspace files.
  • src/workspace/mod.rs
    • Introduced TOOLS_SEED and BOOTSTRAP_SEED constants for the default content of the new files.
    • Modified system_prompt_for_context to inject BOOTSTRAP.md content first (if present and non-empty) and TOOLS.md content (if present and non-empty) into the system prompt.
    • Updated the seed_if_empty function to include TOOLS.md in the default seeded files and to conditionally seed BOOTSTRAP.md only for fresh workspaces.
    • Implemented the import_from_directory asynchronous function to read and import .md files from a given directory into the workspace database, ensuring existing files are not overwritten.
Activity
  • No human activity (comments, reviews, or progress updates) has been recorded for this pull request yet.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/workspace/mod.rs Outdated

let path = entry.path();
// Only import .md files
if path.extension().is_none_or(|ext| ext != "md") {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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")) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in eb270dd — switched to stable path.extension() != Some(OsStr::new("md")).

Comment thread src/workspace/mod.rs Outdated
Comment on lines +866 to +875
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 { .. })
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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 { .. }));

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in eb270dd — using tokio::join! for the three reads now.

Comment thread src/workspace/mod.rs Outdated
Comment on lines +918 to +920
let entry = entry.map_err(|e| WorkspaceError::IoError {
reason: format!("failed to read directory entry: {}", e),
})?;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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;
                }
            };

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in eb270dd — now logs a warning and continues instead of failing the whole import.

Comment thread src/workspace/mod.rs Outdated
Comment on lines +942 to +944
let content = std::fs::read_to_string(&path).map_err(|e| WorkspaceError::IoError {
reason: format!("failed to read {}: {}", path.display(), e),
})?;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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;
                }
            };

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Copy link
Copy Markdown
Collaborator

@nickpismenkov nickpismenkov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@henrypark133 henrypark133 merged commit 5f84155 into main Mar 3, 2026
13 checks passed
@henrypark133 henrypark133 deleted the feat/workspace-markdown-support branch March 3, 2026 03:00
bkutasi pushed a commit to bkutasi/ironclaw that referenced this pull request Mar 28, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: medium Business logic, config, or moderate-risk modules scope: docs Documentation scope: tool/builtin Built-in tools scope: workspace Persistent memory / workspace size: L 200-499 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants