Delfhos
How-to Guides

Goal-oriented guides for accomplishing specific tasks. Assumes basic familiarity with Delfhos. Jump directly to the section that solves your problem.

Connect to a SQL Database

Connect an agent to PostgreSQL, MySQL, or MariaDB. The agent can inspect schemas, run SELECT queries, and execute writes.

python
from delfhos import Agent, SQL

# Option A: connection URL
db = SQL(url="postgresql://user:password@localhost:5432/mydb")

# Option B: individual parameters
db = SQL(
    host="db.example.supabase.co",
    port=5432,
    database="postgres",
    user="postgres",
    password="secret",
    db_type="postgresql",   # or "mysql" / "mariadb"
)

agent = Agent(tools=[db], llm="claude-opus-4-7")
result = agent.run("How many users signed up last week?")
print(result.text)
agent.stop()
Available actions
schema — inspect table schemas and column definitions
query — execute SELECT queries
write — execute INSERT, UPDATE, DELETE statements

Restrict to read-only

python
db = SQL(url="...", allow=["schema", "query"])

Connect to Google Sheets

Read and write spreadsheet data, create new sheets, apply formatting, and build charts.

python
from delfhos import Agent, SQL, Sheets

sheets = Sheets(oauth_credentials="client_secrets.json")
db = SQL(url="postgresql://...")

agent = Agent(tools=[db, sheets], llm="gpt-5.4")

result = agent.run(
    "Pull last month's revenue by region from the database "
    "and write it to the 'Revenue Q3' sheet, creating it if it doesn't exist."
)
agent.stop()
readwritecreateformatchartbatch

Connect to Google Drive

Search, upload, share, and manage files. Use allow and confirm to scope permissions precisely.

python
from delfhos import Agent, Drive, Gmail

drive = Drive(
    oauth_credentials="client_secrets.json",
    allow=["search", "get", "create", "update"],
    confirm=["create", "update"],
)

agent = Agent(
    tools=[drive, Gmail(oauth_credentials="client_secrets.json")],
    llm="gemini-3.1-flash-lite-preview",
)

result = agent.run(
    "Find all PDF files in the 'Reports/Q3' folder and email them to finance@company.com"
)
agent.stop()
searchgetcreateupdatedeletelist_permissionsshareunshare

Connect to Google Docs & Calendar

Create and edit documents, manage calendar events, and combine these with web search in a single agent.

python
from delfhos import Agent, Docs, Calendar, WebSearch

docs     = Docs(oauth_credentials="client_secrets.json")
calendar = Calendar(oauth_credentials="client_secrets.json")
search   = WebSearch(llm="gpt-5.4")

agent = Agent(
    tools=[docs, calendar, search],
    llm="claude-sonnet-4-6",
)

agent.run(
    "Research the latest Python packaging best practices online "
    "and write a summary document called 'Python Packaging Guide'."
)

agent.run(
    "Find a free 30-minute slot this Friday afternoon "
    "and create a calendar event called 'Team Sync'."
)

agent.stop()
Docs actions
readcreateupdateformatdelete
Calendar actions
listgetcreateupdatedeleterespond

Connect to any REST API

APITool turns any OpenAPI 3.x specification into a set of callable agent actions. The compiler reads the spec and registers every endpoint automatically.

From a public spec URL

python
from delfhos import Agent, APITool

petstore = APITool(
    spec="https://petstore3.swagger.io/api/v3/openapi.json",
    allow=["list_pets", "get_pet_by_id"],
    confirm=["add_pet", "delete_pet"],
)

agent = Agent(tools=[petstore], llm="claude-sonnet-4-6")
agent.run("List all available pets and show their names")
agent.stop()

From a local spec with auth headers

python
# Local YAML spec with Bearer token auth
internal = APITool(
    spec="./openapi.yaml",
    base_url="https://api.internal.corp/v1",
    headers={"Authorization": "Bearer sk_..."},
)

# Query-parameter auth
external = APITool(
    spec="https://api.example.com/openapi.json",
    params={"api_key": "my-key"},
)

Fixed path parameters (multi-tenant APIs)

Use path_params to inject fixed values into URL path templates. The values are URL-encoded and substituted automatically — the LLM never sees or passes them.

python
multitenant = APITool(
    spec="https://api.example.com/openapi.json",
    headers={"Authorization": "Bearer sk_..."},
    path_params={"globalCompanyId": "acme-corp"},
)

Discover available endpoints

python
print(APITool.inspect(spec="https://petstore3.swagger.io/api/v3/openapi.json"))
# → {"tool": "petstore3", "methods": ["list_pets", "add_pet", ...], "total": 19}

print(APITool.inspect(spec="./openapi.yaml", verbose=True))

Cache compiled specs for large APIs

python
api = APITool(
    spec="https://api.stripe.com/openapi.json",
    headers={"Authorization": "Bearer sk_live_..."},
    cache=True,  # Saved to ~/delfhos/api_cache/
)

LLM enrichment

python
import os
from delfhos import Agent, APITool

finnhub = APITool(
    spec="https://finnhub.io/static/swagger.json",
    headers={"X-Finnhub-Token": os.environ["FINNHUB_API_KEY"]},
    cache=True,
    enrich=True,
    llm="gemini-3.1-flash-lite-preview",
)

agent = Agent(tools=[finnhub], llm="gemini-3.1-flash-lite-preview")
agent.run("What is the current price of AAPL?")
agent.stop()

Use Local or Custom OpenAI-compatible Models

Use LLMConfig to configure native providers and any OpenAI-compatible custom endpoint — local models, open-source providers, or enterprise servers.

python
from delfhos import Agent, LLMConfig

# Local Ollama model
agent = Agent(tools=[...], llm=LLMConfig(model="llama3.2", base_url="http://localhost:11434/v1"))

# LM Studio
agent = Agent(tools=[...], llm=LLMConfig(
    model="lmstudio-community/Meta-Llama-3-8B-Instruct-GGUF",
    base_url="http://localhost:1234/v1",
))

# Groq (cloud, OpenAI-compatible)
agent = Agent(tools=[...], llm=LLMConfig(
    model="llama-3.3-70b-versatile",
    base_url="https://api.groq.com/openai/v1",
    api_key="gsk_...",
))

# Enterprise server with multiple auth headers
agent = Agent(tools=[...], llm=LLMConfig(
    model="llama-3-70b",
    base_url="https://llm.corp.internal/v1",
    headers={"X-Tenant-ID": "acme-prod", "X-User-Token": "tok_abc123"},
))

Mix local and cloud in a single agent

python
agent = Agent(
    tools=[...],
    light_llm=LLMConfig(model="qwen2.5:7b", base_url="http://localhost:11434/v1"),
    heavy_llm="gemini-3.1-flash-lite-preview",
)

Use Multiple LLMs for Different Tasks

Save money by routing different tasks to different models. Use a fast cheap model for tool selection and a powerful model for code generation.

Quick recipe — cost optimization

python
from delfhos import Agent, SQL, Gmail, Sheets, Drive

agent = Agent(
    tools=[SQL(...), Gmail(...), Sheets(...), Drive(...)],
    light_llm="claude-sonnet-4-6",
    heavy_llm="gpt-5.4",
    enable_prefilter=True,
)
# Result: 60% fewer tokens in code generation, 2–3× lower cost

With specialized overrides

python
agent = Agent(
    tools=[...],
    light_llm="claude-sonnet-4-6",
    heavy_llm="gpt-5.4",
    code_llm="gpt-5.4",
    vision_llm="gpt-5.4",
)

Control Tool Permissions with allow and confirm

Two independent parameters that let you define what a tool can do and whether a human must approve it before it runs.

allow

Defines which actions the agent is permitted to use at all. Actions not in the list are hidden from the LLM.

Enforced before code generation

confirm

Defines which actions must be approved by a human before they execute. The agent can plan them, but execution pauses until you approve or reject.

Enforced before execution

Common patterns

python
# Read-only, no prompts
Gmail(oauth_credentials="...", allow=["read"], confirm=False)

# Full access, fully autonomous
SQL(url="...", confirm=False)

# Full access, approve everything
Drive(oauth_credentials="...", confirm=True)

# Full access, approve only destructive actions
Calendar(oauth_credentials="...", confirm=["delete"])

On @tool functions

python
from delfhos import tool

@tool(confirm=False)         # always runs automatically
def get_account_balance(account_id: str) -> float:
    """Return the current balance for an account."""
    ...

@tool(confirm=True)          # always pauses for approval
def transfer_funds(from_id: str, to_id: str, amount: float) -> bool:
    """Transfer funds between accounts."""
    ...

Require Human Approval Before Actions

Three modes: interactive terminal prompt, custom callback, or programmatic API for background agents.

Custom approval handler

python
def my_approval_handler(request):
    if "delete" in request.message.lower():
        return False      # Always auto-reject deletes
    return True           # Auto-approve everything else

agent = Agent(
    tools=[Gmail(oauth_credentials="...", confirm=True)],
    llm="claude-sonnet-4-6",
    on_confirm=my_approval_handler,
)

Programmatic approval (background agents)

python
agent = Agent(tools=[...], llm="claude-sonnet-4-6")
agent.start()
agent.run_async("Draft and send weekly reports")

# In a web handler or another thread:
pending = agent.get_pending_approvals()
for req in pending:
    agent.approve(req["request_id"], response="Looks good!")
    # or agent.reject(req["request_id"], reason="Wrong recipient")

Run an Agent Asynchronously

Use arun() for async/await workflows, or run_async() to submit a task in the background without blocking.

async/await with arun()

python
import asyncio
from delfhos import Agent, Gmail

async def main():
    agent = Agent(
        tools=[Gmail(oauth_credentials="client_secrets.json")],
        llm="claude-sonnet-4-6",
    )
    result = await agent.arun("Summarize unread emails", timeout=60.0)
    print(result.text)
    agent.stop()

asyncio.run(main())

Context manager for automatic cleanup

python
async def main():
    with Agent(tools=[Gmail(oauth_credentials="...")], llm="claude-sonnet-4-6") as agent:
        result = await agent.arun("Summarize unread emails")
        print(result.text)
# agent.stop() called automatically

Use Two Accounts of the Same Type

Instantiate any connection type multiple times by giving each instance a unique name.

python
from delfhos import Agent, Gmail

work = Gmail(oauth_credentials="work_oauth.json", name="work_email")
personal = Gmail(oauth_credentials="personal_oauth.json", name="personal_email")

agent = Agent(tools=[work, personal], llm="claude-sonnet-4-6")
agent.run("Forward the invoice from my work inbox to my personal email address.")
agent.stop()

Any built-in connection type can be instantiated multiple times as long as each has a unique name.

Enable Tool Prefiltering to Reduce Costs

When you have many tools, a fast model pre-selects only the relevant subset before the expensive code generation step.

python
from delfhos import Agent, Gmail, Sheets, Drive, SQL, WebSearch

agent = Agent(
    tools=[Gmail(...), Sheets(...), Drive(...), SQL(...), WebSearch(...)],
    light_llm="claude-sonnet-4-6",
    heavy_llm="claude-sonnet-4-6",
    enable_prefilter=True,
)

agent.run("What is the weather in London?")
# Prefilter selects: [WebSearch] — Gmail/Sheets/Drive/SQL excluded
Typical result: ~60% fewer tokens in the code generation prompt.

Add Long-term Memory to an Agent

Persist facts across program restarts using semantic search. Relevant facts are injected automatically before each task.

python
from delfhos import Agent, Memory

memory = Memory(
    namespace="crm_agent",
    embedding_model="all-MiniLM-L6-v2",
)

memory.save("""
Alice Chen — VP Sales, alice@acme.com, Enterprise tier
Bob Torres — Dev Lead, bob@acme.com, Pro tier
""")

agent = Agent(tools=[...], llm="claude-sonnet-4-6", memory=memory)
agent.run("Draft a response to Alice's support ticket")

Load from a file

python
memory.add("knowledge_base.md")

Cost Tracking & Budgets

Delfhos tracks token usage and estimates costs automatically. Pricing lives in ~/delfhos/pricing.json.

Read cost after a run

python
result = agent.run("...")
print(f"${result.cost_usd:.5f}")
print(agent.usage)

Set a budget limit

python
agent = Agent(tools=[...], llm="gpt-5.4", budget_usd=0.50)

# Raises AGT-006 if accumulated cost reaches $0.50
agent.reset_budget()       # Reset counter, keep limit
agent.reset_budget(1.00)   # Reset counter and set new limit

Add a System Prompt

Inject a persistent persona, behavioral guardrails, or output format instructions into every LLM call.

python
agent = Agent(
    tools=[SQL(url="..."), Gmail(oauth_credentials="...")],
    llm="claude-sonnet-4-6",
    system_prompt="""
You are a data analyst for Acme Corp.
- Always cite the SQL query you used.
- Prefer charts over raw numbers when sharing results.
- Never email results to external addresses without explicit confirmation.
""",
)

Configure the Execution Sandbox

Delfhos executes LLM-generated code in an isolated sandbox. By default it auto-detects Docker and uses the strongest isolation available.

python
# Default — auto-detects Docker, falls back gracefully
agent = Agent(tools=[SQL(url="...")], llm="claude-sonnet-4-6")

# Force Docker (fails if Docker is not running)
agent = Agent(tools=[SQL(url="...")], llm="claude-sonnet-4-6", sandbox="docker")

# Pin to local sandbox (no Docker required)
agent = Agent(tools=[SQL(url="...")], llm="claude-sonnet-4-6", sandbox="local")

Resource limits (Docker mode only)

python
agent = Agent(
    tools=[...],
    llm="claude-sonnet-4-6",
    sandbox="docker",
    sandbox_config={
        "memory_limit": "1g",
        "cpu_limit":    2.0,
        "timeout":      600,
        "network":      False,
        "pids_limit":   128,
    },
)

Pass input files to the agent workspace

Inject local files into the sandbox so the agent's generated code can read them directly.

python
from delfhos import Agent, SQL

agent = Agent(
    tools=[SQL(url="postgresql://...")],
    llm="claude-sonnet-4-6",
    files=[
        "/data/sales_q3.csv",
        "/data/product_catalog.xlsx",
        "/config/mapping.json",
    ],
)

result = agent.run(
    "Read the sales CSV, join it with the product catalog, "
    "and write a summary to the database."
)
agent.stop()
Note: Files passed via files= are read-only. To produce new files, use add_to_output_files().

Extract output files from a task result

When the agent needs to return a file, it calls add_to_output_files() inside generated code. After the task completes, the files are available on result.files.

python
result = agent.run(
    "Query the top 100 customers by revenue last month "
    "and export the data as a CSV file."
)

if result.files:
    for label, path in result.files.items():
        print(f"{'{'}label{'}'} → {'{'}path{'}'}")
        # → top_customers: /tmp/delfhos_out_abc123/top_customers.csv

Retry on Failure

On each failure the error message is fed back to the LLM so it can generate corrected code.

python
agent = Agent(
    tools=[...],
    llm="gemini-3.1-flash-lite-preview",
    retry_count=3,   # Retry up to 3 times on non-fatal errors
)

The default is retry_count=1 (no retry).

Use rerun() for Replanning

Stop mid-way to hand back what the agent learned at runtime, and ask for a fresh code-generation pass for remaining work.

rerun() is built-in inside every generated script. Use it when the agent cannot write correct code for the next step without first inspecting an API's dynamic response.

python
# Basic pattern
async def main():
    data = await my_api(params, desc="fetching report...")
    sample = data["rows"][0] if data.get("rows") else {}

    rerun(
        context=f"columns={'{'}data['columns']{'}'}, sample={'{'}repr(sample)[:400]{'}'}",
        remaining="Format data['rows'] as a markdown table using the exact columns."
    )

await main()