Yet Another Agent Framework — a Python library for building scheduled, memory-aware, tool-using AI agents.
Define agents as markdown files. Write tools as plain Python functions. yaaf2 handles the rest: the LLM loop, tool execution, memory, sessions, scheduling, and inbound messages.
pip install yaaf2[openai,supabase,discord]Or just OpenAI with no persistence:
pip install yaaf2[openai]from yaaf2 import Agent
from yaaf2.llm import OpenAIProvider
agent = Agent(
agents_dir="agents/",
tools_dir="tools/",
llm=OpenAIProvider(),
)
# Run one task
result = agent.run_solo("alice", "Summarize today's AI news in 3 bullets.")
print(result.final_response)
# Or run on a schedule forever
agent.run_schedule("schedule.md")Each agent is a folder:
agents/
└── alice/
├── soul.md # Who the agent is
└── skills/
├── standup.md # Context appended for standup sessions
└── research.md # Context appended for research sessions
soul.md
---
id: alice
name: Alice
role: Research assistant
description: Scans for trends and summarizes them clearly.
capabilities:
- research
- summarization
---
You are Alice, a research assistant. Be concise. Use bullet points.
When you complete a task, store a memory of what you did.Skills are appended to the system prompt automatically based on the session type.
A tool is a Python file with two things:
# tools/search_web.py
SCHEMA = {
"type": "function",
"function": {
"name": "search_web",
"description": "Search the web for a query.",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string"}
},
"required": ["query"],
},
},
}
def execute(agent_id: str, query: str) -> str:
# ... do the search
return resultsTools can also be async def execute(...). yaaf2 detects and handles both.
Discovery order (later overrides earlier on name collision):
- Built-in tools (memory, learnings)
- Shared
tools/directory - Agent-local
agents/<id>/tools/
yaaf2 ships four tools out of the box. Agents can call these without any setup:
| Tool | Description |
|---|---|
store_memory |
Persist an experience or observation |
recall_memories |
Query past memories |
write_learning |
Record a durable insight with a confidence score |
query_learnings |
Retrieve relevant learnings |
These require a memory= and learnings= backend to be configured.
Define your schedule in a markdown file:
# schedule.md
```python
[
{
"time": "09:00",
"type": "solo",
"agent": "alice",
"session_type": "research",
"task": "Scan for AI trends and summarize the top 3."
},
{
"time": "11:00",
"type": "meeting",
"agents": ["alice", "bob"],
"session_type": "brainstorm",
"task": "Brainstorm content ideas based on today's trends."
},
{
"interval_minutes": 60,
"type": "solo",
"agent": "alice",
"session_type": "solo",
"task": "Check for anything urgent and note it."
},
]run_schedule() also handles catch-up: on startup it replays any tasks missed in the last 3 hours.
Pass a backend to enable persistence:
from yaaf2.memory.backends.supabase import SupabaseMemory
from yaaf2.learnings.backends.supabase import SupabaseLearnings
from yaaf2.sessions.backends.supabase import SupabaseSessionStore
agent = Agent(
agents_dir="agents/",
llm=OpenAIProvider(),
memory=SupabaseMemory(),
learnings=SupabaseLearnings(),
sessions=SupabaseSessionStore(),
)All three are backed by abstract base classes. Swap the backend by subclassing:
from yaaf2.memory.base import MemoryStore
class PostgresMemory(MemoryStore):
def store(self, agent_id, type, summary, full_content, ...): ...
def query(self, agent_id, type=None, tags=None, limit=10): ...from yaaf2.comms.discord import DiscordAdapter
agent = Agent(
...
comms=DiscordAdapter(
channels={"general": "CHANNEL_ID", "standup": "CHANNEL_ID"},
channel_agents={"CHANNEL_ID": "alice"},
),
)yaaf2 polls Discord in a background thread. Messages are routed to agents by @mention or channel default, then dispatched as inbox_request solo runs.
conversation = agent.run_meeting(
agent_ids=["alice", "bob"],
task="Debate the best content format for this week.",
max_turns=10,
)Agents take turns in a round-robin. The meeting ends when any agent signals conclusion or max_turns is reached.
yaaf2 uses the OpenAI SDK, which is compatible with any OpenAI-compatible API:
from yaaf2.llm import OpenAIProvider
# OpenAI
llm = OpenAIProvider()
# DeepSeek
llm = OpenAIProvider(api_key="sk-...", base_url="https://api.deepseek.com")
# Local (Ollama, LM Studio, etc.)
llm = OpenAIProvider(api_key="ollama", base_url="http://localhost:11434/v1")If you're using the Supabase backends, create these tables:
create table memories (
id uuid primary key default gen_random_uuid(),
agent_id text not null,
type text not null,
summary text,
full_content text,
emotional_valence text default 'neutral',
tags text[] default '{}',
created_at timestamptz default now()
);
create table learnings (
id uuid primary key default gen_random_uuid(),
agent_id text not null,
type text not null,
statement text not null,
confidence float default 0.7,
tags text[] default '{}',
evidence_refs text[] default '{}',
source_session_id uuid,
created_at timestamptz default now()
);
create table sessions (
id uuid primary key default gen_random_uuid(),
type text not null,
participants text[] default '{}',
initiator text,
intent text,
status text default 'in_progress',
conversation jsonb default '[]',
artifacts jsonb default '{}',
created_at timestamptz default now(),
completed_at timestamptz
);| Variable | Description |
|---|---|
OPENAI_API_KEY |
OpenAI API key |
SUPABASE_URL |
Supabase project URL |
SUPABASE_KEY |
Supabase anon/service key |
DISCORD_BOT_TOKEN |
Default Discord bot token |
DISCORD_<AGENT_ID>_BOT_TOKEN |
Per-agent Discord token |
MIT