> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/nearai/ironclaw/llms.txt
> Use this file to discover all available pages before exploring further.

# Workspace Module

> Persistent memory storage with filesystem-like structure and hybrid search

## Overview

The workspace provides persistent memory for agents with a flexible filesystem-like structure. Agents can create arbitrary markdown file hierarchies that get indexed for full-text and semantic search.

Inspired by OpenClaw, the workspace gives agents long-term memory across sessions.

## Filesystem-like Structure

```text theme={null}
workspace/
├── README.md              <- Root runbook/index
├── MEMORY.md              <- Long-term curated memory
├── HEARTBEAT.md           <- Periodic checklist
├── IDENTITY.md            <- Agent name, vibe, personality
├── SOUL.md                <- Core values and principles
├── AGENTS.md              <- Behavior instructions
├── USER.md                <- User context and preferences
├── context/               <- Identity and context
│   ├── vision.md
│   └── priorities.md
├── daily/                 <- Daily logs
│   ├── 2024-01-15.md
│   └── 2024-01-16.md
└── projects/              <- Arbitrary structure
    └── alpha/
        ├── README.md
        └── notes.md
```

## Core Types

### Workspace

Database-backed memory storage scoped to a user and optionally an agent.

<ParamField path="user_id" type="String">
  User identifier from the channel
</ParamField>

<ParamField path="agent_id" type="Option<Uuid>">
  Optional agent ID for multi-agent isolation
</ParamField>

<ParamField path="embeddings" type="Option<Arc<dyn EmbeddingProvider>>">
  Embedding provider for semantic search
</ParamField>

#### Constructors

<ResponseField name="new" type="fn(user_id: impl Into<String>, pool: Pool) -> Self">
  Create a new workspace backed by PostgreSQL (requires `postgres` feature)
</ResponseField>

<ResponseField name="new_with_db" type="fn(user_id: impl Into<String>, db: Arc<dyn Database>) -> Self">
  Create a workspace with any Database implementation (libSQL, etc.)
</ResponseField>

<ResponseField name="with_agent" type="fn(self, agent_id: Uuid) -> Self">
  Set a specific agent ID for multi-agent isolation
</ResponseField>

<ResponseField name="with_embeddings" type="fn(self, provider: Arc<dyn EmbeddingProvider>) -> Self">
  Set the embedding provider for semantic search
</ResponseField>

#### Example

```rust theme={null}
use ironclaw::workspace::Workspace;
use ironclaw::workspace::OpenAiEmbeddings;

// Create workspace with embeddings
let embeddings = Arc::new(OpenAiEmbeddings::new(api_key));
let workspace = Workspace::new_with_db("user_123", db)
    .with_embeddings(embeddings);
```

## File Operations

Workspace provides a simple filesystem-like API for document management.

### read

<ResponseField name="read" type="async fn(&self, path: &str) -> Result<MemoryDocument>">
  Read a file by path. Returns error if file doesn't exist.
</ResponseField>

```rust theme={null}
let doc = workspace.read("context/vision.md").await?;
println!("{}", doc.content);
```

### write

<ResponseField name="write" type="async fn(&self, path: &str, content: &str) -> Result<MemoryDocument>">
  Create or update a file. Creates parent directories implicitly. Re-indexes for search.
</ResponseField>

```rust theme={null}
workspace.write(
    "projects/alpha/README.md",
    "# Project Alpha\n\nDescription here."
).await?;
```

### append

<ResponseField name="append" type="async fn(&self, path: &str, content: &str) -> Result<()>">
  Append content to a file. Creates the file if it doesn't exist. Adds newline separator.
</ResponseField>

```rust theme={null}
workspace.append("MEMORY.md", "Learned about Rust async patterns today.").await?;
```

### exists

<ResponseField name="exists" type="async fn(&self, path: &str) -> Result<bool>">
  Check if a file exists at the given path.
</ResponseField>

```rust theme={null}
if !workspace.exists("TODO.md").await? {
    workspace.write("TODO.md", "# Tasks\n").await?;
}
```

### delete

<ResponseField name="delete" type="async fn(&self, path: &str) -> Result<()>">
  Delete a file and its associated search chunks.
</ResponseField>

```rust theme={null}
workspace.delete("outdated/notes.md").await?;
```

### list

<ResponseField name="list" type="async fn(&self, directory: &str) -> Result<Vec<WorkspaceEntry>>">
  List files and directories at a path. Returns immediate children (not recursive). Use empty string or "/" for root.
</ResponseField>

```rust theme={null}
let entries = workspace.list("projects/").await?;
for entry in entries {
    if entry.is_directory {
        println!("📁 {}/", entry.name());
    } else {
        println!("📄 {}", entry.name());
    }
}
```

### list\_all

<ResponseField name="list_all" type="async fn(&self) -> Result<Vec<String>>">
  List all files recursively as a flat list of paths.
</ResponseField>

```rust theme={null}
let all_files = workspace.list_all().await?;
println!("Total files: {}", all_files.len());
```

## Convenience Methods

### memory

<ResponseField name="memory" type="async fn(&self) -> Result<MemoryDocument>">
  Get the main MEMORY.md document (long-term curated memory). Creates if it doesn't exist.
</ResponseField>

```rust theme={null}
let memory = workspace.memory().await?;
println!("Current memory: {}", memory.content);
```

### today\_log

<ResponseField name="today_log" type="async fn(&self) -> Result<MemoryDocument>">
  Get today's daily log (append-only, keyed by date).
</ResponseField>

```rust theme={null}
let today = workspace.today_log().await?;
```

### daily\_log

<ResponseField name="daily_log" type="async fn(&self, date: NaiveDate) -> Result<MemoryDocument>">
  Get a daily log for a specific date.
</ResponseField>

```rust theme={null}
use chrono::NaiveDate;

let date = NaiveDate::from_ymd_opt(2024, 1, 15).unwrap();
let log = workspace.daily_log(date).await?;
```

### append\_memory

<ResponseField name="append_memory" type="async fn(&self, entry: &str) -> Result<()>">
  Append an entry to MEMORY.md with double newline separation. For important facts and decisions.
</ResponseField>

```rust theme={null}
workspace.append_memory("User prefers tabs over spaces.").await?;
```

### append\_daily\_log

<ResponseField name="append_daily_log" type="async fn(&self, entry: &str) -> Result<()>">
  Append a timestamped entry to today's daily log.
</ResponseField>

```rust theme={null}
workspace.append_daily_log("Completed initial project setup.").await?;
// Stored as: [14:35:22] Completed initial project setup.
```

### heartbeat\_checklist

<ResponseField name="heartbeat_checklist" type="async fn(&self) -> Result<Option<String>>">
  Get the HEARTBEAT.md checklist for periodic background tasks. Returns seed template if not yet created.
</ResponseField>

```rust theme={null}
if let Some(checklist) = workspace.heartbeat_checklist().await? {
    println!("Heartbeat tasks:\n{}", checklist);
}
```

## System Prompt

### system\_prompt

<ResponseField name="system_prompt" type="async fn(&self) -> Result<String>">
  Build the system prompt from identity files (AGENTS.md, SOUL.md, USER.md, IDENTITY.md, MEMORY.md). Includes last 2 days of daily logs.
</ResponseField>

```rust theme={null}
let prompt = workspace.system_prompt().await?;
```

### system\_prompt\_for\_context

<ResponseField name="system_prompt_for_context" type="async fn(&self, is_group_chat: bool) -> Result<String>">
  Build system prompt with option to exclude MEMORY.md for group chats (privacy protection).
</ResponseField>

```rust theme={null}
// Exclude personal memory in group contexts
let prompt = workspace.system_prompt_for_context(true).await?;
```

## Search

### search

<ResponseField name="search" type="async fn(&self, query: &str, limit: usize) -> Result<Vec<SearchResult>>">
  Hybrid search combining full-text (BM25) and semantic (vector) search using Reciprocal Rank Fusion.
</ResponseField>

```rust theme={null}
let results = workspace.search("project deadlines", 5).await?;
for result in results {
    println!("{}: {} (score: {})", result.path, result.preview, result.score);
}
```

### search\_with\_config

<ResponseField name="search_with_config" type="async fn(&self, query: &str, config: SearchConfig) -> Result<Vec<SearchResult>>">
  Search with custom configuration for ranking weights and limits.
</ResponseField>

```rust theme={null}
use ironclaw::workspace::SearchConfig;

let config = SearchConfig::default()
    .with_limit(10)
    .with_semantic_weight(0.7)  // Prefer semantic over full-text
    .with_bm25_weight(0.3);

let results = workspace.search_with_config("async patterns", config).await?;
```

## Seeding

### seed\_if\_empty

<ResponseField name="seed_if_empty" type="async fn(&self) -> Result<usize>">
  Seed missing core identity files (README, MEMORY, IDENTITY, SOUL, AGENTS, USER, HEARTBEAT). Only creates files that don't exist - never overwrites. Returns number of files created.
</ResponseField>

```rust theme={null}
let created = workspace.seed_if_empty().await?;
if created > 0 {
    println!("Created {} workspace files", created);
}
```

### backfill\_embeddings

<ResponseField name="backfill_embeddings" type="async fn(&self) -> Result<usize>">
  Generate embeddings for chunks that don't have them yet. Useful after enabling embedding provider. Returns number of chunks processed.
</ResponseField>

```rust theme={null}
let count = workspace.backfill_embeddings().await?;
println!("Generated embeddings for {} chunks", count);
```

## Data Types

### MemoryDocument

A document stored in the workspace.

<ParamField path="id" type="Uuid">
  Unique document identifier
</ParamField>

<ParamField path="user_id" type="String">
  Owner user identifier
</ParamField>

<ParamField path="agent_id" type="Option<Uuid>">
  Optional agent ID for isolation
</ParamField>

<ParamField path="path" type="String">
  File path (e.g., "context/vision.md")
</ParamField>

<ParamField path="content" type="String">
  Full document content
</ParamField>

<ParamField path="created_at" type="DateTime<Utc>">
  Creation timestamp
</ParamField>

<ParamField path="updated_at" type="DateTime<Utc>">
  Last update timestamp
</ParamField>

### WorkspaceEntry

An entry in a directory listing.

<ParamField path="path" type="String">
  Relative path from listing directory
</ParamField>

<ParamField path="is_directory" type="bool">
  True if this entry has children
</ParamField>

<ParamField path="updated_at" type="Option<DateTime<Utc>>">
  Last update time (latest among children for directories)
</ParamField>

<ParamField path="content_preview" type="Option<String>">
  First \~200 characters (None for directories)
</ParamField>

### SearchResult

A search result with ranking score.

<ParamField path="path" type="String">
  Document path
</ParamField>

<ParamField path="chunk_content" type="String">
  Matched chunk content
</ParamField>

<ParamField path="score" type="f32">
  Combined relevance score (0.0 to 1.0)
</ParamField>

<ParamField path="preview" type="String">
  Preview text with context
</ParamField>

### SearchConfig

Configuration for hybrid search ranking.

<ParamField path="limit" type="usize" default="10">
  Maximum results to return
</ParamField>

<ParamField path="semantic_weight" type="f32" default="0.5">
  Weight for semantic similarity (0.0 to 1.0)
</ParamField>

<ParamField path="bm25_weight" type="f32" default="0.5">
  Weight for BM25 full-text score (0.0 to 1.0)
</ParamField>

<ParamField path="k" type="usize" default="60">
  RRF constant (higher = less aggressive fusion)
</ParamField>

## Embedding Providers

Workspace supports multiple embedding providers for semantic search.

### OpenAiEmbeddings

```rust theme={null}
use ironclaw::workspace::OpenAiEmbeddings;

let embeddings = Arc::new(OpenAiEmbeddings::new(api_key));
```

### OllamaEmbeddings

```rust theme={null}
use ironclaw::workspace::OllamaEmbeddings;

let embeddings = Arc::new(OllamaEmbeddings::new(
    "http://localhost:11434",
    "nomic-embed-text"
));
```

### NearAiEmbeddings

```rust theme={null}
use ironclaw::workspace::NearAiEmbeddings;

let embeddings = Arc::new(NearAiEmbeddings::new(base_url, api_key));
```

### MockEmbeddings

For testing without external dependencies.

```rust theme={null}
use ironclaw::workspace::MockEmbeddings;

let embeddings = Arc::new(MockEmbeddings::new());
```

## Key Patterns

1. **Memory is persistence**: If you want to remember something, write it to the workspace
2. **Flexible structure**: Create any directory/file hierarchy you need
3. **Self-documenting**: Use README.md files to describe directory structure
4. **Hybrid search**: Vector similarity + BM25 full-text via RRF for best results
5. **Privacy boundaries**: Use `system_prompt_for_context(true)` to exclude personal memory in group chats

## Related Modules

<Card title="Agent Module" icon="robot" href="/api/agent">
  Core agent orchestration and session management
</Card>

<Card title="LLM Module" icon="brain" href="/api/llm">
  Language model integration for reasoning
</Card>
