Imported from open1s/bos (
AGENTS.md). Install upstream withnpx skills add open1s/bos. Copyright stays with the author.
BOS Agent Instructions
High-signal facts for working in this repo.
Workspace Structure
crates/
├── agent/ # Core agent: tools, skills, hooks, plugins, sessions, memory
├── bus/ # Pub/sub, queryable, caller/callable
├── config/ # TOML/YAML config loading
├── jsbos/ # Node.js bindings (NAPI-RS)
├── logging/ # Tracing, instrumentation
├── nbos/ # Python bindings (cdylib, maturin)
├── qserde/ # rkyv archive support used by the bus wire format
├── qserde_derive/ # Derive macros for qserde
├── react/ # ReAct engine, LLM integration
└── resource/ # Standalone resource subsystem + `rex` CLI (no dependents)
Version Control
This repo use jj(jujutsu) for version management Core Concepts
- change_id: Unique identifier for a change (use this, NOT commit hash)
- stack: Ordered list of changes (your working history)
- working copy: Always attached to a change
Key difference from Git: jj is stack-based, not branch-based. You are expected to edit, reorder, and clean history before pushing.
# Status
jj status
# Create new change
jj new
# Describe change
jj describe -m "<crate>: <title>"
# View stack
jj log
# Edit change
jj edit <change_id>
# Split change
jj split
# Squash into parent
jj squash
# Reorder changes
jj rebase -r <change_id> -d <destination>
Essential Commands
# Build the workspace
cargo build --workspace
# Test single crate / whole workspace
cargo test -p <crate>
cargo test --workspace
# Lint (warnings are errors) and format
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all
# API docs (fails on any missing-docs warning)
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps
# Standalone resource CLI
cargo run -p rex -- --help
# Python binding (crates/nbos)
cd crates/nbos && maturin develop
# Node.js binding (crates/jsbos)
cd crates/jsbos && npm install && npm run build
Crate Dependencies
Each layer depends only on the layers to its right:
nbos / jsbos → agent → react → bus → logging → config
qserde/qserde_deriveprovide the rkyv-backed wire format thatbusserializes payloads with.resourceis an independent subsystem; nothing in the agent stack depends on it (it pullsbus/config/loggingonly for its optionalclifeature).
Key: Agent-to-agent messaging and pub/sub flow through bus. Direct
request/response work (LLM calls, tool execution) flows through the trait seams
in react (LlmClient, Tool/AsyncTool), not the bus.
Python/JS Bindings
| Bindings | Entry | Build |
|---|---|---|
| Python | crates/nbos/ |
maturin develop |
| JS | crates/jsbos/index.js |
npm run build |
User guides: docs/python-user-guide.md, docs/javascript-user-guide.md, docs/rust-user-guide.md
Unified API (Python ↔ JS)
Python and JavaScript APIs are designed to be consistent:
# Python
from nbos import BrainOS, tool
@tool("Add")
def add(a, b): return a + b
async with BrainOS() as brain:
agent = brain.agent("assistant").register(add)
result = await agent.ask("What is 2+2?")
// JavaScript
const { BrainOS, ToolDef } = require('brainos');
const addTool = new ToolDef('add', 'Add', (args) => args.a + args.b, ...);
const brain = new BrainOS();
await brain.start();
const agent = brain.agent('assistant').register(addTool);
const result = await agent.ask('What is 2+2?');
Testing Notes
- Use
#[tokio::test]for async tests - Run with:
cargo test -p <crate> name -- --nocapture - Set
RUST_LOG=debugfor tracing output - Current green baselines:
- Rust (excluding the binding crates): 443 passed, 0 failed, 8 ignored
- Python (
cd crates/nbos && pytest -m "not llm"): 229 passed, 4 deselected - JS (
cd crates/jsbos && npx ava): 94 tests (13 parity, 3 native-binding, 21 API-doc, 9 content, 14 memory)
Key Patterns
- Tools: Wrap a closure with
FunctionTool(sync) orAsyncFunctionTool(async, may await I/O), or implementTool/AsyncTooldirectly; register withToolRegistry::register/register_async; the agent invokes them through thereactseam (LlmClient,Tool), never the bus. - Bus: Build a primitive and attach a session.
- Publisher:
Publisher::new(topic).with_session(session)?.publish(&payload) - Subscriber:
Subscriber::new(topic).with_session(session)thenrecv()/recv_with_timeout(d)/run(handler) - Query:
Query::new(topic).with_session(session)?.query(&req)against aQueryable::new(topic).with_handler(..)orwith_stream_handler(..) - RPC:
Caller::new(name, session).call(&req)against aCallable::new(uri, session).with_handler(..)thenstart() - Raw topic access:
session.publish(topic, &payload)/session.subscribe(topic, handler)
- Publisher:
- Memory:
agent::memory::MemoryStorestores and recalls text across turns;InMemoryMemoryranks items by keyword overlap and breaks ties toward recency. The trait is object-safe, so a vector or file backend implements the same seam.FileMemorypersists the same ranking to a JSON-lines file so memories survive a restart. Attach one withAgent::with_memoryand matching items are appended to the system prompt on each run;Agent::recalled_contextexposes the block on its own, andAgent::remember/forget/recallare the ergonomic path to the store.MetadataFilterscopes recall to a metadata key/value andFileMemory::with_max_itemscaps a file store. Python and JS expose the same store as aMemoryclass, pinned to the Rust ranking by the sharedmemory_ranking.jsonfixture;AgentBuilder.with_memory(Python) /withMemory(JS) prepends recalled matches to each text run, bounded by a recall limit.Memory.searchaccepts a metadata filter andwith_max_items/withMaxItemscaps the store.Memory.save/Memory.loadread and write the same JSON-lines format as the RustFileMemory. - Templates:
react::templaterenders{{name}}prompts withPromptTemplate/render_templateand places recall withwith_memory; the agent substitutes a{{memory}}placeholder in the system prompt and appends the block when there is none. - Bindings: Python and JS share one canonical vocabulary —
publish_text/publish_json,ask/ask_json,call/call_json,handle/start/run/run_json,recv/recv_json. Python keeps the legacycreate_*/publish_*names as deprecated aliases for backward compatibility; never remove them. Three static guards incrates/jsbos/test/keep the surfaces honest:parity.test.jscompares the wrapper classes across languages,native-bindings.test.jschecks that every wrapper call on a native object resolves to a real member of the generated interface, andapi-docs.test.jschecks that every public binding member is documented indocs/api-reference/. - Config: Use
ConfigLoader.discover()for auto-loading~/.bos/conf/config.toml - Documentation: Every crate carries
#![warn(missing_docs)]at its root, so the workspace must stay at zero missing docs. Document new public items in the same change. The sole exception isresource/src/action.rs, which suppresses the lint for the rkyv-generated resolver enum. - Solutions:
docs/solutions/— documented solutions (bugs, best practices, patterns), organized by category with YAML frontmatter (module,tags,problem_type)
