agentfs 0.2.0

Agent Persistence
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
<h1 align="center">
  <br>
  AGENTFS
  <br>
</h1>

<h4 align="center">
  Filesystem Abstraction for AI Agents
</h4>

<p align="center">
  <a href="https://crates.io/crates/agentfs" target="_blank">
    <img src="https://img.shields.io/crates/v/agentfs" alt="Crates.io"/>
  </a>
  <a href="https://crates.io/crates/agentfs" target="_blank">
    <img src="https://img.shields.io/crates/d/agentfs" alt="Downloads"/>
  </a>
  <a href="https://docs.rs/agentfs" target="_blank">
    <img src="https://docs.rs/agentfs/badge.svg" alt="Documentation"/>
  </a>
  <a href="LICENSE" target="_blank">
    <img src="https://img.shields.io/github/license/cryptopatrick/agentfs.svg" alt="License"/>
  </a>
</p>

<b>Author's bio:</b> ๐Ÿ‘‹๐Ÿ˜€ Hi, I'm CryptoPatrick! I'm currently enrolled as an
Undergraduate student in Mathematics, at Chalmers & the University of Gothenburg, Sweden. <br>
If you have any questions or need more info, then please <a href="https://discord.gg/T8EWmJZpCB">join my Discord Channel: AiMath</a>

---

<p align="center">
  <a href="#-what-is-agentfs">What is AgentFS</a> โ€ข
  <a href="#-features">Features</a> โ€ข
  <a href="#-architecture">Architecture</a> โ€ข
  <a href="#-how-to-use">How To Use</a> โ€ข
  <a href="#-documentation">Documentation</a> โ€ข
  <a href="#-license">License</a>
</p>

## ๐Ÿ›Ž Important Notices
* **POSIX-like** filesystem operations for AI agents
* **Multi-backend** support: SQLite, PostgreSQL, MySQL
* **Tool call auditing** built-in
* **Zero vendor lock-in** - fully open source

<!-- TABLE OF CONTENTS -->
<h2 id="table-of-contents"> :pushpin: Table of Contents</h2>

<details open="open">
  <summary>Table of Contents</summary>
  <ol>
    <li><a href="#-what-is-agentfs">What is AgentFS</a></li>
    <li><a href="#-features">Features</a></li>
      <ul>
        <li><a href="#-filesystem-operations">Filesystem Operations</a></li>
        <li><a href="#-key-value-store">Key-Value Store</a></li>
        <li><a href="#-tool-call-auditing">Tool Call Auditing</a></li>
      </ul>
    <li><a href="#-architecture">Architecture</a></li>
    <li><a href="#-how-to-use">How to Use</a></li>
    <li><a href="#-examples">Examples</a></li>
    <li><a href="#-testing">Testing</a></li>
    <li><a href="#-documentation">Documentation</a></li>
    <li><a href="#-author">Author</a></li>
    <li><a href="#-support">Support</a></li>
    <li><a href="#-license">License</a>
  </ol>
</details>

## ๐Ÿค” What is AgentFS

`agentfs` provides a high-level filesystem abstraction for AI agents, offering POSIX-like file operations, key-value storage, and tool call auditing. It enables agents to persist state, store generated files, and maintain audit trails across sessions.

Built on top of the [agentdb](../agentdb) abstraction layer and [agentsql](../agentsql) SQL backends, AgentFS provides a complete storage solution for AI agents with zero vendor lock-in.

### Use Cases

- **Agent Workspaces**: Provide agents with isolated filesystem workspaces for storing outputs
- **Multi-Agent Systems**: Share data between agents through a common filesystem
- **Tool Call Auditing**: Track all agent actions with built-in audit logging
- **State Management**: Store agent configuration and session state in KV store
- **Output Storage**: Persist agent-generated documents, reports, and artifacts
- **Cloud Deployment**: Deploy on managed databases (AWS RDS, Google Cloud SQL, Azure)

## ๐Ÿ“ท Features

`agentfs` provides three high-level APIs for AI agent storage with production-grade features:

### ๐Ÿ“ Filesystem Operations

**POSIX-like Interface**:
- **write_file(path, data)**: Create or overwrite files with automatic parent directory creation
- **read_file(path)**: Read complete file contents into memory
- **mkdir(path)**: Create directories recursively
- **readdir(path)**: List directory contents with metadata
- **remove(path)**: Delete files and directories recursively
- **exists(path)**: Check if path exists
- **stat(path)**: Get file metadata (size, timestamps, permissions, type)

**Advanced Features**:
- **Symbolic Links**: Create and follow symlinks transparently
- **Path Normalization**: Automatic path cleaning and validation
- **Inode/Dentry Design**: Unix-like filesystem structure for reliability
- **Concurrent Access**: Safe multi-agent filesystem sharing with locking
- **Mount Point Isolation**: Sandboxed /agent root for security

### ๐Ÿ—„๏ธ Key-Value Store

**Simple API**:
- **set(key, value)**: Store arbitrary key-value pairs
- **get(key)**: Retrieve values by key
- **delete(key)**: Remove keys permanently
- **scan(prefix)**: Find all keys matching a prefix
- **exists(key)**: Check key existence

**Use Cases**:
- Session state management for agent conversations
- Agent configuration and settings storage
- Caching computed results for performance
- Metadata storage for files and operations

### ๐Ÿ“Š Tool Call Auditing

**Workflow-Based API**:
- **start(name, params)**: Begin tracking a tool call with parameters
- **success(id, result)**: Mark tool call as successful with optional result
- **error(id, error)**: Mark tool call as failed with error message

**Single-Shot API**:
- **record(name, start, end, params, result, error)**: Record completed tool call in one operation

**Analytics**:
- Get per-tool statistics (total calls, success rate, average duration)
- List recent tool calls with filtering by name, status, time range
- Track tool execution timelines and patterns
- Debug agent behavior with complete audit trail

## ๐Ÿ“ Architecture

1. ๐Ÿ› **Overall Architecture**

```diagram
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              Application Layer                      โ”‚
โ”‚   โ€ข Agent frameworks (Rig, LangChain, custom)      โ”‚
โ”‚   โ€ข Multi-agent systems                            โ”‚
โ”‚   โ€ข CLI tools and services                         โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                  AgentFS APIs                       โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚  โ”‚  FileSystem  โ”‚   KvStore   โ”‚  ToolRecorder    โ”‚ โ”‚
โ”‚  โ”‚              โ”‚             โ”‚                  โ”‚ โ”‚
โ”‚  โ”‚ โ€ข mkdir      โ”‚ โ€ข set       โ”‚ โ€ข start          โ”‚ โ”‚
โ”‚  โ”‚ โ€ข write_file โ”‚ โ€ข get       โ”‚ โ€ข success        โ”‚ โ”‚
โ”‚  โ”‚ โ€ข read_file  โ”‚ โ€ข delete    โ”‚ โ€ข error          โ”‚ โ”‚
โ”‚  โ”‚ โ€ข readdir    โ”‚ โ€ข scan      โ”‚ โ€ข record         โ”‚ โ”‚
โ”‚  โ”‚ โ€ข remove     โ”‚ โ€ข exists    โ”‚ โ€ข statistics     โ”‚ โ”‚
โ”‚  โ”‚ โ€ข stat       โ”‚             โ”‚ โ€ข recent         โ”‚ โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              AgentDB Trait Interface                โ”‚
โ”‚  โ€ข Database-agnostic operations                     โ”‚
โ”‚  โ€ข put, get, delete, scan, query                    โ”‚
โ”‚  โ€ข Transaction support                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚               AgentSQL (SQLx)                       โ”‚
โ”‚  โ€ข Connection pooling                               โ”‚
โ”‚  โ€ข Migration system                                 โ”‚
โ”‚  โ€ข Type-safe SQL                                    โ”‚
โ”‚  โ€ข Multi-backend support                            โ”‚
โ””โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
    โ”‚                โ”‚                โ”‚
โ”Œโ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  SQLite  โ”‚  โ”‚ PostgreSQL  โ”‚  โ”‚   MySQL    โ”‚
โ”‚  Local   โ”‚  โ”‚  Production โ”‚  โ”‚   Cloud    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
```

2. ๐Ÿ—‚๏ธ **Filesystem Layer Architecture**

```diagram
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              FileSystem API Calls                   โ”‚
โ”‚   mkdir("/docs")  write_file("/docs/a.txt")         โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              Path Resolution                        โ”‚
โ”‚  โ€ข Normalize paths (remove .., .)                   โ”‚
โ”‚  โ€ข Validate against mount point (/agent)            โ”‚
โ”‚  โ€ข Split into parent + name components              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              Inode Operations                       โ”‚
โ”‚  โ€ข Lookup parent directory inode                    โ”‚
โ”‚  โ€ข Create new inode for file/dir                    โ”‚
โ”‚  โ€ข Update metadata (size, mtime, permissions)       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              Dentry Operations                      โ”‚
โ”‚  โ€ข Insert (parent_ino, name, ino) entry             โ”‚
โ”‚  โ€ข UNIQUE constraint ensures no duplicates          โ”‚
โ”‚  โ€ข Enables path-to-inode lookup                     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              Data Storage (for files)               โ”‚
โ”‚  โ€ข Store file content in fs_data table              โ”‚
โ”‚  โ€ข Chunk large files by offset                      โ”‚
โ”‚  โ€ข Link to inode via foreign key                    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              SQLite / PostgreSQL / MySQL            โ”‚
โ”‚  โ€ข fs_inode:  metadata (size, times, mode)          โ”‚
โ”‚  โ€ข fs_dentry: name resolution                       โ”‚
โ”‚  โ€ข fs_data:   file contents                         โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
```

3. ๐Ÿ”„ **Tool Call Workflow**

```diagram
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚           Agent Calls Tool (e.g., web_search)       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚     tools.start("web_search", params)               โ”‚
โ”‚  โ€ข Generate UUID for tool call                      โ”‚
โ”‚  โ€ข Store: name, params, started_at                  โ”‚
โ”‚  โ€ข Status: "pending"                                โ”‚
โ”‚  โ€ข Returns: call_id                                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ”‚
       โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
       โ”‚                           โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”      โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Tool Succeeds  โ”‚      โ”‚   Tool Fails       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜      โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚                           โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ tools.success(id, res)  โ”‚  โ”‚ tools.error(id, err)     โ”‚
โ”‚ โ€ข Update completed_at   โ”‚  โ”‚ โ€ข Update completed_at    โ”‚
โ”‚ โ€ข Store result JSON     โ”‚  โ”‚ โ€ข Store error message    โ”‚
โ”‚ โ€ข Status: "success"     โ”‚  โ”‚ โ€ข Status: "error"        โ”‚
โ”‚ โ€ข Calculate duration_ms โ”‚  โ”‚ โ€ข Calculate duration_ms  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
          โ”‚                            โ”‚
          โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                       โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              Persisted in tool_calls table            โ”‚
โ”‚  โ€ข Query by name, status, time range                  โ”‚
โ”‚  โ€ข Generate statistics (success rate, avg duration)   โ”‚
โ”‚  โ€ข Audit trail for debugging and compliance           โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
```

4. ๐Ÿ’พ **Storage Implementation**

```diagram
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                   AgentFS Instance                  โ”‚
โ”‚  โ€ข agent_name: "my-agent"                           โ”‚
โ”‚  โ€ข mount_point: "/agent"                            โ”‚
โ”‚  โ€ข db: Box<dyn AgentDB>                             โ”‚
โ”‚                                                     โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚            FileSystem Component              โ”‚  โ”‚
โ”‚  โ”‚  Uses: fs_inode, fs_dentry, fs_data tables  โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                                                     โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚             KvStore Component                โ”‚  โ”‚
โ”‚  โ”‚  Uses: kv_store table                        โ”‚  โ”‚
โ”‚  โ”‚  Prefix: "kv:{agent_name}:"                  โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                                                     โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚           ToolRecorder Component             โ”‚  โ”‚
โ”‚  โ”‚  Uses: tool_calls table                      โ”‚  โ”‚
โ”‚  โ”‚  All calls tagged with agent_name            โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚               Database Backend                      โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚  fs_inode: ino, mode, size, times            โ”‚  โ”‚
โ”‚  โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  โ”‚
โ”‚  โ”‚  fs_dentry: parent_ino, name, ino            โ”‚  โ”‚
โ”‚  โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  โ”‚
โ”‚  โ”‚  fs_data: ino, offset, size, data (BLOB)     โ”‚  โ”‚
โ”‚  โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  โ”‚
โ”‚  โ”‚  kv_store: key, value, timestamps            โ”‚  โ”‚
โ”‚  โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  โ”‚
โ”‚  โ”‚  tool_calls: id, name, params, result, ...   โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
```

## ๐Ÿš™ How to Use

### Requirements
- Rust 1.70 or higher
- Database backend (SQLite, PostgreSQL, or MySQL)

### Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
agentfs = "0.1"
agentsql = { version = "0.1", features = ["sqlite"] }

# For PostgreSQL:
# agentsql = { version = "0.1", features = ["postgres"] }

# For MySQL:
# agentsql = { version = "0.1", features = ["mysql"] }
```

Or install with cargo:

```bash
cargo add agentfs
cargo add agentsql --features sqlite
```

### Example: SQLite (Local Development)

```rust
use agentfs::{AgentFS, FileSystem, KvStore, ToolRecorder};
use agentsql::SqlBackend;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create AgentFS with SQLite backend
    let backend = SqlBackend::sqlite("agent.db").await?;
    let agent_fs = AgentFS::new(Box::new(backend), "my-agent", "/agent").await?;

    // Filesystem operations
    agent_fs.fs.mkdir("/output").await?;
    agent_fs.fs.write_file("/output/report.txt", b"Hello, World!").await?;

    let content = agent_fs.fs.read_file("/output/report.txt").await?.unwrap();
    println!("File content: {}", String::from_utf8_lossy(&content));

    // List directory
    let entries = agent_fs.fs.readdir("/output").await?;
    for entry in entries {
        println!("{}: {} bytes", entry.name, entry.size);
    }

    // Key-value store
    agent_fs.kv.set("config:theme", b"dark").await?;
    let theme = agent_fs.kv.get("config:theme").await?.unwrap();
    println!("Theme: {}", String::from_utf8_lossy(&theme));

    // Tool call auditing
    let id = agent_fs.tools.start("web_search", Some(serde_json::json!({
        "query": "Rust async programming"
    }))).await?;

    // Simulate search...
    agent_fs.tools.success(id, Some(serde_json::json!({
        "results": 10,
        "duration_ms": 123
    }))).await?;

    // Get statistics
    let stats = agent_fs.tools.statistics("web_search").await?;
    println!("Success rate: {:.1}%", stats.success_rate * 100.0);

    Ok(())
}
```

### Example: PostgreSQL (Production)

```rust
use agentfs::AgentFS;
use agentsql::SqlBackend;
use std::env;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Connect to PostgreSQL
    let database_url = env::var("DATABASE_URL")
        .unwrap_or_else(|_| "postgres://user:pass@localhost/agentfs".to_string());

    let backend = SqlBackend::postgres(database_url).await?;
    let agent_fs = AgentFS::new(Box::new(backend), "prod-agent", "/agent").await?;

    // Same API as SQLite!
    agent_fs.fs.write_file("/logs/app.log", b"System started").await?;

    // Scan KV store
    agent_fs.kv.set("session:user123", b"active").await?;
    let sessions = agent_fs.kv.scan("session:").await?;
    println!("Found {} active sessions", sessions.len());

    Ok(())
}
```

### Example: MySQL (Cloud Deployment)

```rust
use agentfs::AgentFS;
use agentsql::SqlBackend;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Connect to MySQL (e.g., AWS Aurora)
    let backend = SqlBackend::mysql(
        "mysql://user:pass@aurora-cluster.region.rds.amazonaws.com/agentfs"
    ).await?;

    let agent_fs = AgentFS::new(Box::new(backend), "cloud-agent", "/agent").await?;

    // Multi-agent coordination
    agent_fs.fs.mkdir("/shared").await?;
    agent_fs.fs.write_file("/shared/status.json", b"{\"status\":\"ready\"}").await?;

    // List recent tool calls across all agents
    let recent = agent_fs.tools.recent(10).await?;
    for call in recent {
        println!("{}: {} ({})", call.name, call.status, call.duration_ms);
    }

    Ok(())
}
```

## ๐Ÿงช Examples

The repository includes three comprehensive examples demonstrating different backends and use cases:

### Example 1: Basic Operations (SQLite)

See [`examples/basic.rs`](examples/basic.rs) for a complete example demonstrating:
- File write/read operations
- Directory management
- KV store usage
- Tool call recording with start/success/error workflow
- Statistics gathering

Run with:
```bash
cargo run --example basic
```

### Example 2: PostgreSQL Multi-Agent System

See [`examples/postgres.rs`](examples/postgres.rs) for:
- Concurrent file operations from multiple agents
- Shared state across agents using KV store
- PostgreSQL-specific features and configuration
- Production deployment patterns

Run with:
```bash
export DATABASE_URL="postgres://user:password@localhost/agentfs_demo"
cargo run --example postgres --features postgres
```

### Example 3: MySQL Cloud Deployment

See [`examples/mysql.rs`](examples/mysql.rs) for:
- Cloud deployment patterns (AWS Aurora, Google Cloud SQL)
- Production workflow with tool auditing
- Multi-agent coordination example
- MySQL-specific optimizations

Run with:
```bash
export DATABASE_URL="mysql://user:password@localhost/agentfs_demo"
cargo run --example mysql --features mysql
```

## ๐Ÿงช Testing

Run the comprehensive test suite:

```bash
# Run all tests (SQLite)
cargo test

# Run tests with output
cargo test -- --nocapture

# Run with all backends
cargo test --all-features

# Test specific backend
cargo test --features postgres
cargo test --features mysql
```

The test suite includes:
- Filesystem operations (mkdir, write, read, remove, readdir)
- Path normalization and validation
- KV store operations (set, get, delete, scan)
- Tool call workflow (start, success, error, record)
- Statistics and analytics
- Concurrent access patterns
- Error handling

## ๐Ÿ“š Documentation

Comprehensive documentation is available at [docs.rs/agentfs](https://docs.rs/agentfs), including:
- Complete API reference for FileSystem, KvStore, and ToolRecorder
- Architecture overview and design decisions
- Migration guide from other agent filesystems
- Performance optimization tips
- Multi-agent coordination patterns
- Backend selection guide (SQLite vs PostgreSQL vs MySQL)

## ๐ŸŽฏ Comparison

| Feature | AgentFS | Other Solutions |
|---------|---------|-----------------|
| **Backend Choice** | SQLite, PostgreSQL, MySQL | Vendor-specific |
| **Open Source** | โœ… MIT Licensed | โš ๏ธ Varies |
| **Self-Hosted** | โœ… Yes | โŒ Cloud-only |
| **Tool Auditing** | โœ… Built-in | โŒ Not included |
| **Zero Cost** | โœ… Yes | โŒ Usage-based pricing |
| **Local Development** | โœ… SQLite | โš ๏ธ Requires cloud account |
| **POSIX-like API** | โœ… Yes | โš ๏ธ Limited |
| **Multi-Agent** | โœ… Native support | โš ๏ธ Requires workarounds |

## ๐Ÿ–Š Author

<a href="https://x.com/cryptopatrick">CryptoPatrick</a>

Keybase Verification:
https://keybase.io/cryptopatrick/sigs/8epNh5h2FtIX1UNNmf8YQ-k33M8J-Md4LnAN

## ๐Ÿฃ Support

Leave a โญ if you think this project is cool.

## ๐Ÿ—„ License

This project is licensed under MIT. See [LICENSE](LICENSE) for details.