Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,26 @@ Read more about the motivation for AgentFS in the announcement [blog post](https
Initialize an agent filesystem:

```bash
$ agentfs init
Created agent filesystem: agent.db
$ agentfs init my-agent
Created agent filesystem: .agentfs/my-agent.db
Agent ID: my-agent
```

Inspect the agent filesystem from outside:
Inspect the agent filesystem:

```bash
$ agentfs fs ls
$ agentfs fs ls my-agent
Using agent: my-agent
f hello.txt

$ agentfs fs cat hello.txt
$ agentfs fs cat my-agent hello.txt
hello from agent
```

You can also use a database path directly:

```bash
$ agentfs fs cat .agentfs/my-agent.db hello.txt
hello from agent
```

Expand Down Expand Up @@ -80,7 +89,12 @@ Use it in your agent code:
```typescript
import { AgentFS } from 'agentfs-sdk';

const agent = await AgentFS.open('./agent.db');
// Persistent storage with identifier
const agent = await AgentFS.open({ id: 'my-agent' });
// Creates: .agentfs/my-agent.db

// Or use ephemeral in-memory database
const ephemeralAgent = await AgentFS.open();

// Key-value operations
await agent.kv.set('user:preferences', { theme: 'dark' });
Expand Down
99 changes: 66 additions & 33 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,8 @@ struct Args {
enum Commands {
/// Initialize a new agent filesystem
Init {
/// SQLite file to create (default: agent.db)
#[arg(default_value = "agent.db")]
filename: PathBuf,
/// Agent identifier (if not provided, generates a unique one)
id: Option<String>,

/// Overwrite existing file if it exists
#[arg(long)]
Expand Down Expand Up @@ -83,51 +82,87 @@ enum Commands {
enum FsCommands {
/// List files in the filesystem
Ls {
/// Filesystem to use (default: agent.db)
#[arg(long = "filesystem", default_value = "agent.db")]
filesystem: PathBuf,
/// Agent ID or database path
id_or_path: String,

/// Path to list (default: /)
#[arg(default_value = "/")]
path: String,
fs_path: String,
},
/// Display file contents
Cat {
/// Filesystem to use (default: agent.db)
#[arg(long = "filesystem", default_value = "agent.db")]
filesystem: PathBuf,
/// Agent ID or database path
id_or_path: String,

/// Path to the file
path: String,
/// Path to the file in the filesystem
file_path: String,
},
}

async fn init_database(db_path: &Path, force: bool) -> AnyhowResult<()> {
// Check if file already exists
fn resolve_agent_id(id_or_path: String) -> AnyhowResult<(String, PathBuf)> {
let agentfs_dir = Path::new(".agentfs");

// Check if it looks like a path (contains / or ends with .db)
if id_or_path.contains('/') || id_or_path.ends_with(".db") {
// Treat as a database path
let db_path = PathBuf::from(&id_or_path);
if !db_path.exists() {
anyhow::bail!("Database '{}' not found", db_path.display());
}
let id = db_path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("unknown")
.to_string();
Ok((id, db_path))
} else {
// Treat as an agent ID
let db_path = agentfs_dir.join(format!("{}.db", id_or_path));
if !db_path.exists() {
anyhow::bail!("Agent '{}' not found at '{}'", id_or_path, db_path.display());
}
Ok((id_or_path, db_path))
}
}

async fn init_database(id: Option<String>, force: bool) -> AnyhowResult<()> {
use agentfs_sdk::AgentFSOptions;
use std::time::{SystemTime, UNIX_EPOCH};

// Generate ID if not provided
let id = id.unwrap_or_else(|| {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
format!("agent-{}", timestamp)
});

// Check if agent already exists
let db_path = Path::new(".agentfs").join(format!("{}.db", id));
if db_path.exists() && !force {
anyhow::bail!(
"File '{}' already exists. Use --force to overwrite.",
"Agent '{}' already exists at '{}'. Use --force to overwrite.",
id,
db_path.display()
);
}

let db_path_str = db_path.to_str().context("Invalid database path")?;

// Use the SDK to initialize the database - this ensures consistency
// with how `agentfs run` initializes the database
AgentFS::new(db_path_str)
// The SDK will create .agentfs directory and database file
AgentFS::open(AgentFSOptions::with_id(&id))
.await
.context("Failed to initialize database")?;

eprintln!("Created agent filesystem: {}", db_path.display());
eprintln!("Agent ID: {}", id);

Ok(())
}

async fn ls_filesystem(db_path: &Path, path: &str) -> AnyhowResult<()> {
if !db_path.exists() {
anyhow::bail!("Filesystem '{}' does not exist", db_path.display());
}
async fn ls_filesystem(id: Option<String>, path: &str) -> AnyhowResult<()> {
let (agent_id, db_path) = resolve_agent_id(id)?;
eprintln!("Using agent: {}", agent_id);

let db_path_str = db_path.to_str().context("Invalid filesystem path")?;

Expand Down Expand Up @@ -212,10 +247,8 @@ async fn ls_filesystem(db_path: &Path, path: &str) -> AnyhowResult<()> {
Ok(())
}

async fn cat_filesystem(db_path: &Path, path: &str) -> AnyhowResult<()> {
if !db_path.exists() {
anyhow::bail!("Filesystem '{}' does not exist", db_path.display());
}
async fn cat_filesystem(id: Option<String>, path: &str) -> AnyhowResult<()> {
let (_agent_id, db_path) = resolve_agent_id(id)?;

let db_path_str = db_path.to_str().context("Invalid filesystem path")?;

Expand Down Expand Up @@ -326,23 +359,23 @@ async fn main() {
let args = Args::parse();

match args.command {
Commands::Init { filename, force } => {
if let Err(e) = init_database(&filename, force).await {
Commands::Init { id, force } => {
if let Err(e) = init_database(id, force).await {
eprintln!("Error: {}", e);
std::process::exit(1);
}
std::process::exit(0);
}
Commands::Fs { command } => match command {
FsCommands::Ls { filesystem, path } => {
if let Err(e) = ls_filesystem(&filesystem, &path).await {
FsCommands::Ls { id_or_path, fs_path } => {
if let Err(e) = ls_filesystem(id_or_path, &fs_path).await {
eprintln!("Error: {}", e);
std::process::exit(1);
}
std::process::exit(0);
}
FsCommands::Cat { filesystem, path } => {
if let Err(e) = cat_filesystem(&filesystem, &path).await {
FsCommands::Cat { id_or_path, file_path } => {
if let Err(e) = cat_filesystem(id_or_path, &file_path).await {
eprintln!("Error: {}", e);
std::process::exit(1);
}
Expand Down
4 changes: 2 additions & 2 deletions examples/claude-agent/research-assistant/.env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Anthropic API Key (required)
ANTHROPIC_API_KEY=your-api-key-here

# Optional: Custom AgentFS database path
# AGENTFS_DB=agentfs.db
# Optional: Custom AgentFS agent ID
# AGENTFS_ID=research-assistant
6 changes: 2 additions & 4 deletions examples/claude-agent/research-assistant/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@ dist/
.env
.env.local

# AgentFS database
agentfs.db
agentfs.db-shm
agentfs.db-wal
# AgentFS local databases
.agentfs/

# IDE
.vscode/
Expand Down
4 changes: 2 additions & 2 deletions examples/claude-agent/research-assistant/src/utils/agentfs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ let instance: AgentFS | null = null;

export async function getAgentFS(): Promise<AgentFS> {
if (!instance) {
const dbPath = process.env.AGENTFS_DB || 'agentfs.db';
instance = await AgentFS.open(dbPath);
const id = process.env.AGENTFS_ID || 'research-assistant';
instance = await AgentFS.open({ id });
}
return instance;
}
3 changes: 3 additions & 0 deletions examples/mastra/research-assistant/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ dist
.env
*.db
*.db-*

# AgentFS local databases
.agentfs/
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ let instance: AgentFS | null = null;

export async function getAgentFS(): Promise<AgentFS> {
if (!instance) {
const dbPath = process.env.AGENTFS_DB || 'agentfs.db';
instance = await AgentFS.open(dbPath);
const id = process.env.AGENTFS_ID || 'research-assistant';
instance = await AgentFS.open({ id });
}
return instance;
}
Expand Down
4 changes: 2 additions & 2 deletions examples/openai-agents/research-assistant/.env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# OpenAI API Key (required)
OPENAI_API_KEY=your-api-key-here

# Optional: Custom AgentFS database path
# AGENTFS_DB=agentfs.db
# Optional: Custom AgentFS agent ID
# AGENTFS_ID=research-assistant
3 changes: 3 additions & 0 deletions examples/openai-agents/research-assistant/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ dist/
.env
*.db
*.log

# AgentFS local databases
.agentfs/
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ let instance: AgentFS | null = null;

export async function getAgentFS(): Promise<AgentFS> {
if (!instance) {
const dbPath = process.env.AGENTFS_DB || 'agentfs.db';
instance = await AgentFS.open(dbPath);
const id = process.env.AGENTFS_ID || 'research-assistant';
instance = await AgentFS.open({ id });
}
return instance;
}
3 changes: 3 additions & 0 deletions sdk/rust/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
target

# AgentFS local databases
.agentfs/
Loading
Loading