<div align="center">
# `Peon`

<br />


[](https://crates.io/crates/peon-core)
[](https://opensource.org/licenses/MIT)
<h3>The AI agent framework that says <em>no</em>.</h3>
<p align="center">
<strong>English</strong> ยท
<a href="README_zh.md">ไธญๆ</a>
</p>
</div>
---
**Other frameworks give LLMs a shell and pray. Peon gives LLMs a leash and proves it works.**
Most agent frameworks (LangChain, AutoGPT, CrewAI) focus on _what_ an AI can do.
Peon focuses on _what it cannot_ โ and enforces that at the architecture level, not the prompt level.
<div align="center">
```
User "3856588331" sends: "Roll a 128-sided die"
โ
read_skill("roll-dice") โ Skill loaded, paths unlocked
โ
execute_script("roll.sh", 128) โ Whitelist check passed, enforcer approved
โ
Agent response: "You rolled **30** on a d128!"
```
```
Same bot, different user (not in policy):
โ
read_skill("roll-dice") โ Skill loaded...
โ ALL PERMISSIONS DENIED โ Path never entered whitelist
โ execute_script("roll.sh", 128) โ SECURITY VIOLATION: not in whitelist
๐ค Agent response: "I cannot execute this script โ permission denied."
```
**Same bot. Same code. Same skill. Different user โ different outcome.**
That's zero-trust.
</div>
---
## โก See It In Action
Here's what actually happens when a Telegram user sends `"Roll a 128-sided die"` to a Peon-powered bot:
```log
INFO peon_telegram Received message from chat ID 3856588331
INFO peon_core::agent User input (uid=3856588331): Roll a 128-sided die for me
INFO peon_runtime::agent Agent run: uid='3856588331'
# Turn 1: LLM discovers the skill
INFO peon_runtime::agent Tool call: read_skill({"skill_name":"roll-dice"})
INFO peon_core::scanner Added to execute whitelist: .../roll-dice/scripts/roll.sh
# Turn 2: LLM executes with the unlocked path
INFO peon_runtime::agent Tool call: execute_script({"path":"...roll.sh","arguments":["128"]})
INFO peon_core::tools Execute access granted for: .../roll-dice/scripts/roll.sh
# Turn 3: Done.
INFO peon_runtime::agent Agent response (turn 3): You rolled a **30** on the 128-sided die.
```
Now change one line in `user_permissions.csv` โ remove the user's access:
```log
INFO peon_core::tools Tool call: read_skill('roll-dice')
WARN peon_core::scanner All permissions denied for path 'roll.sh' โ not added to any whitelist
WARN peon_core::tools SECURITY VIOLATION: './scripts/roll.sh' not in execute whitelist โ blocked
INFO peon_runtime::agent Agent response: "I cannot execute this script โ permission denied."
```
**The LLM retried twice. It tried relative paths. It tried re-reading the skill. Nothing worked.** The path was never whitelisted because the enforcer rejected the user identity at the scan layer โ two layers before execution.
---
## ๐ง How It Works
```
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ
โ LLM Brain โโโโโโถโ Peon Security โโโโโโถโ OS Execution โ
โ (Reasoning) โ โ Matrix (Casbin) โ โ (Scripts/Files) โ
โ โ โ โ โ โ
โ "Execute X" โ โ UID? โ โ โ bash roll.sh 128 โ
โ โ โ Whitelist? โ โ โ โ
โ โ โ File ACL? โ โ โ โ stdout: "30" โ
โ โ โ User ACL? โ โ โ โ
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ
โฒ Fails ANY check?
โ โ Blocked. Period.
```
**Defence in Depth, not Defence by Prompt:**
| Layer | What | Bypass-proof? |
| :----------------- | :---------------------------------------------------------- | :----------------------------------------- |
| **Whitelist** | Only paths discovered from `SKILL.md` are executable | โ
LLM cannot invent paths |
| **File ACL** | `file_permissions.txt` โ system-wide deny/allow rules | โ
Not visible to LLM |
| **User ACL** | `user_permissions.csv` โ per-user RBAC via Casbin | โ
UID injected physically, not via prompt |
| **RequestContext** | UID is passed as a Rust struct, not a task-local or env var | โ
Unforgeable by the LLM |
---
## ๐ฆ Quick Start
### Install
```bash
cargo add peon-core
```
Or from source:
```bash
git clone https://github.com/stephen94125/peon-lib.git
cd peon-lib
cargo build --release
```
### Configure
```dotenv
# .env
PROVIDER=openai # openai | anthropic | gemini | openrouter
MODEL=gpt-4o-mini
API_KEY=sk-...
PEON_SKILLS_DIR=skills
PEON_FILE_PERMISSIONS=file_permissions.txt
PEON_USER_PERMISSIONS=user_permissions.csv
```
### Run
```rust
let agent = PeonAgentBuilder::new().await?.default_prompt().build();
let response = agent.prompt("Roll a 20-sided die", "user_123").await?;
// The UID "user_123" is physically passed to every tool call.
// The LLM cannot forge, override, or escalate it. Period.
```
---
## ๐งฉ Workspace Crates
| Crate | crates.io | Purpose |
| :------------------------------------ | :-------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------- |
| **[`peon-runtime`](peon-runtime/)** | [](https://crates.io/crates/peon-runtime) | Custom LLM runtime โ provider abstraction, agent loop, multimodal messages |
| **[`peon-core`](peon-core/)** | [](https://crates.io/crates/peon-core) | Zero-trust engine โ skill scanner, Casbin enforcers, tool sandboxing |
| **[`peon-cli`](peon-cli/)** | [](https://crates.io/crates/peon-cli) | Unix-style CLI โ pipe stdin, use in CI/CD |
| **[`peon-telegram`](peon-telegram/)** | [](https://crates.io/crates/peon-telegram) | Multi-user Telegram bot with per-user identity isolation |
---
## ๐ก๏ธ Security Model
> [!WARNING]
> **Strict Zero-Trust**: Peon requires `file_permissions.txt` and `user_permissions.csv` to exist. If missing, the agent **panics on startup** โ no silent fallback, no "allow-all" default.
**`file_permissions.txt`** โ What the agent can touch:
```text
x, ./skills/* # Allow execution within skills
!x, /bin/rm # Block rm, always, no exceptions
r, ./data/* # Allow reading data files
!r, ./secrets/* # Block reading secrets
```
**`user_permissions.csv`** โ Who can do what:
```csv
p, *, *, *, allow # Allow everyone (development mode)
p, 3856588331, *, execute, allow # Only this Telegram user can execute
p, admin_role, *, *, allow # Role-based access
g, alice, admin_role # Alice inherits admin permissions
```
---
## ๐ง Skills System
Skills are **Markdown files that define what an LLM is allowed to do** โ not just what it _can_ do.
```
skills/
โโโ roll-dice/
โโโ SKILL.md # Instructions + path declarations
โโโ scripts/
โโโ roll.sh # The actual executable
```
```markdown
---
name: roll-dice
description: Roll dice using a random number generator.
---
To roll a die, execute: `./scripts/roll.sh <sides>`
```
When `read_skill("roll-dice")` is called, Peon:
1. Reads the SKILL.md content
2. Extracts all referenced paths (`./scripts/roll.sh`)
3. Resolves them to absolute paths via `canonicalize()`
4. Checks the enforcer for each path
5. Only whitelisted + enforcer-approved paths get added
**The LLM never sees the filesystem. It only sees what Peon explicitly unlocks.**
---
## ๐บ๏ธ Roadmap
| Status | Feature |
| :----- | :-------------------------------------------------------------------------- |
| โ
| Zero-trust tool execution with dual-layer enforcement |
| โ
| Custom LLM runtime (`peon-runtime`) โ OpenAI, Anthropic, Gemini, OpenRouter |
| โ
| Telegram bot with per-user identity isolation |
| โ
| Skill discovery, dynamic whitelisting, session reset |
| ๐ | **Telegram**: Rich responses โ images, files, formatted messages |
| ๐ | **Telegram**: Multimodal input โ photos, voice, documents |
| ๐ | **Discord**: Bot integration |
| ๐ | **CLI**: End-to-end verification with real API providers |
| ๐๏ธ | WASM runtime support for browser-based agents |
| ๐๏ธ | Persistent conversation memory across sessions |
---
## ๐ค Supported Providers
Peon uses its own runtime (`peon-runtime`) with native support for:
**OpenAI** ยท **Anthropic** ยท **Gemini** ยท **OpenRouter** (access to 200+ models)
---
## ๐ Meta
<a href="https://github.com/stephen94125/peon-lib/graphs/contributors">
<img src="https://contrib.rocks/image?repo=stephen94125/peon-lib" alt="contrib.rocks" />
</a>
Made with [contrib.rocks](https://contrib.rocks).
Licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE).
Read the source README