sAI SDK · version 0.2.0 · macOS
sAI's agents.
Inside your app.
Bring your own model. Get a whole team of coding agents — with tools, approvals and a sandbox — in a few lines of Python.
Quick start
The SDK comes with sAI — install and activate it as usual, then:
from sai_sdk import Agent, LLM
agent = Agent(LLM.openai("gpt-4o-mini", api_key="sk-..."),
folder="~/my-project", allow_edits=True)
for event in agent.run("fix the failing test"):
print(event) # every step as it happens; the last one is the Result
result = agent.ask("what does utils.py do?") # or just the final answer
print(result.answer)
Your model
Use any model with an OpenAI-compatible API. Agents need tool calling to read files, edit and run commands; without it they can still answer questions.
LLM("gpt-4o-mini", api_key=...) # OpenAI
LLM("deepseek-chat", base_url="https://api.deepseek.com/v1", api_key=...)
LLM.openrouter("qwen/qwen3-coder", api_key=...)
LLM.groq("openai/gpt-oss-120b", api_key=...) # free tier
LLM.gemini("gemini-3.5-flash", api_key=...) # free tier
LLM.ollama("qwen2.5-coder:14b") # on your computer, no key
LLM.custom(my_function) # anything else
- Your key is used for calls only and never stored.
- Rate limits and temporary errors are retried automatically, waiting as long as the provider asks.
LLM.custom(fn):fn(messages, tools)receives OpenAI-format messages and returns a string, or{"content": ..., "tool_calls": [...]}.- Smaller models pick agents less reliably — pass
agent="Coder"to choose one yourself.
Agent
| Option | What it does |
|---|---|
llm | Your model (required). |
folder | The project folder. Agents can't reach outside it. |
tools | Your own tools — see Your tools. |
approve | Your function that allows or refuses each file edit and risky command. |
allow_edits | Without approve: apply edits automatically. Risky commands are refused. |
agent | Always use one agent: Coder, Debugger, Researcher, Reviewer, Writer, Planner, Architect, DevOps. |
max_steps | Tool steps per request (default 12). |
sandbox | Run agent commands in the macOS sandbox (default on). |
run(prompt) streams events · ask(prompt) returns the Result ·
reset() starts a new conversation (otherwise it continues) · stop() stops the current run.
Events
| Event | Means |
|---|---|
Notice | Which agent took the request. |
Text | Reply text as it's being written. |
ToolCall | An agent is using a tool, with its arguments. |
ToolResult | What the tool returned, and whether it worked. |
AgentMessage | A complete message; final marks the answer. |
Result | Always last: answer, agent, stop_reason,
declined, models, tokens, error. |
Model and tool failures never crash your program — they arrive as a Result with stop_reason="error".
Approvals
def approve(request):
print(request.kind, request.description) # "edit", "command", ...
print(request.diff) # for edits: exactly what changes
return ask_my_user() # True to allow
agent = Agent(llm, folder="~/my-project", approve=approve)
A refused action isn't retried or worked around — the agent explains what it wanted to do, and
result.declined lists it.
Your tools
from sai_sdk import tool
@tool
def ticket(ticket_id: str) -> str:
"Look up a bug ticket by its id." # what the model sees
return my_tracker.get(ticket_id)
agent = Agent(llm, folder="~/my-project", tools=[ticket])
agent.ask("Fix the bug described in ticket BUG-142.")
Typed parameters become the tool's arguments; those without defaults are required. Return text. If your function raises, the agent is told and can react.
Good to know
- One run at a time per Python process. Run agents one after another, or in separate processes.
- macOS and Python 3.14, like the rest of sAI 0.2.0.
- The SDK needs your activated sAI license, on the computer it was issued for.
- Your code and prompts go only to the model provider you choose.