Getting started
Install
Requires Python 3.11+. The core install already includes FastAPI, uvicorn, SQLAlchemy (async), and asyncpg — a PostgreSQL-backed web deployment needs no extras.
pip install fg-agents # core (FastAPI + PostgreSQL/asyncpg)
pip install "fg-agents[sqlite]" # + SQLite backend (aiosqlite)
pip install "fg-agents[openai]" # + OpenAI provider (covers all OpenAI-compatible)
pip install "fg-agents[anthropic]" # + Anthropic provider
pip install "fg-agents[google]" # + Google Gemini (google-genai)
pip install "fg-agents[templates]" # + Jinja2 for prompt templates
pip install "fg-agents[scheduler]" # + croniter for cron scheduling
pip install "fg-agents[all]" # everything
Note: some ImportError messages mention
pip install "fg-agents[postgres]", but nopostgresextra exists — asyncpg is a core dependency, so PostgreSQL works out of the box.
Imports guarded by optional dependencies (SQLiteRepository, AgentScheduler, the
web/API symbols, …) resolve to None or raise a clear error until the matching extra is
installed.
API keys
Keys are resolved in two ways, in order:
- An explicit override:
AgentLLM(api_keys={"openai": "sk-...", ...}) - Environment variables named
{PROVIDER}_API_KEY— e.g.OPENAI_API_KEY,ANTHROPIC_API_KEY.
Local providers (Ollama, LM Studio, vLLM, …) need no key. One environment gate matters:
the CODE tool type is disabled unless FG_ALLOW_CODE_EXECUTION=true is set — and even
then it is not a security sandbox.
Hello agent — one agent, streamed, no database
import asyncio
from fg_agents import (
AgentDefinition, AgentEngine, AgentLLM, ToolRegistry, create_repository, tool,
)
@tool(description="Greet someone by name")
def greet(name: str) -> str:
return f"Hello, {name}! Welcome to Fareground."
async def main():
llm = AgentLLM()
repo = create_repository("memory") # or "sqlite", "postgres"
await repo.initialize()
tools = ToolRegistry()
tools.register_function(greet)
agent_def = AgentDefinition(
name="greeter",
model="ollama:qwen3:8b", # required — there is no default
system_prompt="You are a friendly greeter. Use the greet tool when asked.",
tools=["greet"],
)
engine = AgentEngine(llm=llm, tool_registry=tools, repository=repo)
async for event in engine.run("session-1", "Say hello to Alice", agent_def):
if event.data.get("text"):
print(event.data["text"], end="", flush=True)
elif event.type.value == "tool.result":
print(f"\n [tool] {event.data['tool_name']}: {event.data['status']}")
asyncio.run(main())
What each piece does:
@toolturns a plain function into a registered tool. The JSON schema is derived from the signature; sync functions run in a thread; an optionalctx: ExecutionContextparameter is auto-injected and excluded from the schema.create_repository("memory")picks the persistence backend."sqlite"and"postgres"are drop-in swaps — the engine only speaksBaseRepository.AgentDefinitionis declarative config.modelhas no default and must be"provider:model";toolsnames must exist in the registry.engine.run(...)is the ReAct loop: it persists the user message, calls the LLM, executes tool calls, and yields typedStreamEvents until the agent finishes.
A full web backend in one call
create_app() wires the whole stack — orchestrator, repository, router, SSE — into a
FastAPI application:
from fg_agents import AgentDefinition, ToolRegistry, create_app, tool
@tool(description="Search the knowledge base")
async def search(query: str) -> str: ...
assistant = AgentDefinition(
name="assistant",
model="anthropic:claude-sonnet-4-6",
system_prompt="You are the product assistant.",
tools=["search"],
)
tools = ToolRegistry()
tools.register_function(search)
app = create_app(
agents={"assistant": assistant},
tool_registry=tools,
# db_url="postgresql+asyncpg://user:pass@localhost:5432/mydb", omitting db_url falls back to a SQLite file (fg_agents.db), not in-memory
)
# run with: uvicorn myapp:app
The HTTP surface (default prefix /api/agent):
| Method | Path | Purpose |
|---|---|---|
POST |
/sessions |
Create a session → { session_id } |
GET |
/sessions/{id} |
Session status and metadata |
DELETE |
/sessions/{id} |
Delete a session |
POST |
/sessions/{id}/messages |
Send a message → SSE stream (or JSON with stream=false) |
POST |
/sessions/{id}/cancel |
Nuclear stop (cascades to sub-agents) |
POST |
/sessions/{id}/resume |
Resume a waiting_input / failed session |
GET |
/sessions/{id}/stream |
Re-attach to an in-flight run (Last-Event-ID replay) |
GET |
/sessions/{id}/messages |
Paginated history (limit / offset) |
GET |
/sessions/{id}/artifacts |
Session artifacts |
GET |
/sessions/{id}/audit |
Audit log |
GET / POST |
/tools |
List tools / register tool (POST admin-gated) |
GET / POST |
/agents |
List agents / register agent (POST admin-gated) |
GET |
/memory/{agent_id} |
Agent persistent memory |
PUT |
/memory/{agent_id}/{key} |
Set a memory key |
GET |
/health |
Health check |
Runs are owned by a process-wide run registry and detached from the HTTP request — a
client disconnect does not cancel the agent; the browser re-attaches via
GET /sessions/{id}/stream.
Without an authenticator, the app runs as a single shared admin tenant and logs a loud
one-time warning — fine for local development, never for production. See the
multi-tenant security model.