Your agent,
ready to ship.
Any model. Built-in tools, memory, and MCP. Serve locally or publish to the Genfleet marketplace — all from one package.
pip install "genfleet-sdk[openai,serve] @ git+https://github.com/genfleet/sdk@dev"
Installation
Install the SDK with pip or uv, from GitHub: it is not on PyPI yet. Core depends only on Pydantic — extras add provider clients and the A2A server. Pin a tag or commit instead of @dev for a fixed version.
shell# Core (pydantic only) pip install "genfleet-sdk @ git+https://github.com/genfleet/sdk@dev" # With OpenAI + A2A server pip install "genfleet-sdk[openai,serve] @ git+https://github.com/genfleet/sdk@dev" # With Anthropic + A2A server pip install "genfleet-sdk[anthropic,serve] @ git+https://github.com/genfleet/sdk@dev" # Everything pip install "genfleet-sdk[all] @ git+https://github.com/genfleet/sdk@dev"
| Extra | Installs | Use for |
|---|---|---|
openai | openai>=1.0 | OpenAI + any OpenAI-compatible endpoint (DeepSeek, Groq, Ollama…) |
anthropic | anthropic>=0.25 | Anthropic Claude models |
gemini | google-genai>=1.0 | Google Gemini models |
memory | redis>=5.0 | Redis-backed session memory |
mcp | mcp>=1.0 | MCP server connections |
serve | fastapi, uvicorn, sse-starlette | A2A HTTP server |
all | all of the above | Full install |
ImportError with install instructions.
Agent Skill
Install the genfleet-sdk skill so your coding agent understands the Agent API and can help you build agents faster.
shellnpx skills add genfleet/sdk
Agent() with tools, MCP servers, or memory — it uses current SDK patterns.@tool correctly with proper type hints and JSON Schema generation.create_app() vs serve(), A2A protocol, and how to test with curl.Quickstart
Create an Agent, give it a model and a tool, then serve it over A2A.
quickstart.pyimport os from genfleet.sdk import Agent from genfleet.sdk.serve import serve def get_weather(city: str) -> str: """Gets the current weather for a city.""" return f"72°F and sunny in {city}" agent = Agent( role="You are a helpful assistant.", model={"model": "openai/gpt-4o-mini", "api_key": os.environ["OPENAI_API_KEY"]}, tools=[get_weather], ) serve(agent, name="my-agent", port=8000)
shell# Start the server python quickstart.py # Call it curl -X POST http://localhost:8000 \ -H "Content-Type: application/json" -H 'A2A-Version: 1.0' \ -d '{"jsonrpc":"2.0","id":1,"method":"SendMessage","params":{"message":{"messageId":"m1","role":"ROLE_USER","parts":[{"text":"What is the weather in Paris?"}]}}}'
Core Concepts
The SDK defines the contract between your agent code and the Genfleet platform.
run() for framework integrations that manage their own LLM calls.@tool or pass plain functions to tools=.ToolProtocol with auto-generated JSON schema from type hints and docstrings.Agent(tools=[...]) are auto-wrapped — @tool is optional. Use it when you want an explicit name or description.
Agent
The main developer-facing class. Implements AgentProtocol — pass it directly to serve().
Wires together a provider, tool dispatch loop, optional Redis memory, and optional MCP connections. The tool loop is built-in: the agent calls tools, feeds results back to the LLM, and repeats until a text response is produced.
pythonAgent( role: str, # system prompt / persona model: ModelConfig, # provider + credentials dict tools: list[Callable] = [], # plain functions — auto-wrapped mcps: list[MCPConfig] = [], # MCP server connections memory: MemoryConfig | "platform" | None = None, # episodic memory (platform store or Redis) context: str | None = None, # extra context appended to system prompt audit: AuditConfig | None = None, # structured audit logging )
ModelConfig
Provider credentials as a plain dict. Use the provider/model-name format to specify the provider explicitly.
python# OpenAI {"model": "openai/gpt-4o-mini", "api_key": "sk-..."} # Anthropic {"model": "anthropic/claude-sonnet-4-6", "api_key": "sk-ant-..."} # Google Gemini {"model": "gemini/gemini-1.5-pro", "api_key": "..."} # Any OpenAI-compatible endpoint (DeepSeek, Groq, Ollama, …) {"model": "openai/deepseek-chat", "api_key": "sk-...", "base_url": "https://api.deepseek.com"}
| Provider prefix | Provider | Required extra |
|---|---|---|
openai/ | OpenAI (+ any compatible endpoint) | [openai] |
anthropic/ | Anthropic | [anthropic] |
gemini/ | Google Gemini | [gemini] |
openai/ + base_url | OpenAI-compatible | [openai] |
MemoryConfig
Redis-backed session memory. History is loaded at the start of each run() and saved at the end. Requires pip install 'genfleet-sdk[memory] @ git+https://github.com/genfleet/sdk@dev'.
python{ "type": "redis", "connection": "redis://localhost:6379/0", "user": "optional", "password": "optional", }
Session ID from input.metadata["session_id"]. Falls back to a per-request UUID when absent. History stored with a 24-hour TTL.
shell — passing session_idcurl -X POST http://localhost:8000 \ -H "Content-Type: application/json" -H 'A2A-Version: 1.0' \ -d '{"jsonrpc":"2.0","id":1,"method":"SendMessage","params":{"message":{"messageId":"m1","role":"ROLE_USER","contextId":"user-123","parts":[{"text":"My name is Alice"}]}}}'
MCPConfig
Connect to MCP servers. Tool schemas are fetched on the first run() call and merged with local tools. Requires pip install 'genfleet-sdk[mcp] @ git+https://github.com/genfleet/sdk@dev'.
python# stdio — spawns a subprocess { "type": "stdio", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"], } # SSE — connects to a running HTTP server { "type": "sse", "url": "http://localhost:3001/sse", }
AuditConfig
Opt-in structured audit logging. Captures invocation lifecycle, LLM calls, tool dispatch, token usage, and errors. No overhead when audit=None (the default).
python — console backendagent = Agent( role="You are a helpful assistant.", model={"model": "openai/gpt-4o-mini", "api_key": os.environ["OPENAI_API_KEY"]}, audit={"backend": "console", "agent_name": "my-agent"}, )
python — file backend (JSONL)audit={"backend": "file", "file_path": "audit.jsonl"}
python — callback backendasync def my_handler(event): print(event.event_type, event.data) audit={"backend": "callback", "callback": my_handler}
| Key | Type | Default | Description |
|---|---|---|---|
backend | str | required | "console", "file", or "callback" |
file_path | str | "audit.jsonl" | Output path (file backend only) |
callback | Callable | required for callback | Sync or async handler function |
max_content_length | int | 500 | Truncation limit for content in events (0 = unlimited) |
events | list[str] | all | Filter which event types to emit |
agent_name | str | None | Name tag included in every event |
Audit Events
| Event | When | Key data |
|---|---|---|
invocation.start | run() called | message, session_id |
llm.request | Before LLM call | model, message_count, tool_count |
llm.response | After LLM responds | token_usage, latency_ms |
tool.call | Before tool dispatch | tool_name, arguments |
tool.result | After tool returns | tool_name, result, latency_ms |
invocation.end | run() completes | total_latency_ms, total_token_usage |
invocation.error | Exception in run() | error_type, message |
Token usage (input/output/total tokens) is tracked automatically for OpenAI, Anthropic, and Gemini providers.
AgentProtocol
The universal agent contract. Use this for framework integrations that manage their own LLM calls. Agent already implements it.
Any object with a matching run method satisfies the protocol. No inheritance needed.
protocol.py@runtime_checkable class AgentProtocol(Protocol): def run(self, input: AgentInput) -> AsyncIterator[AgentOutput]: ...
Direct implementation
my_agent.pyfrom genfleet.sdk import AgentInput, AgentOutput, AgentProtocol from genfleet.sdk.serve import serve class MyAgent: async def run(self, input: AgentInput): yield AgentOutput(content=f"Hello! You said: {input.message}", done=True) assert isinstance(MyAgent(), AgentProtocol) # passes serve(MyAgent(), name="my-agent", port=8000)
ToolProtocol
The contract for tools the agent can invoke.
Any object with schema() and call() satisfies this protocol.
protocol.py@runtime_checkable class ToolProtocol(Protocol): def schema(self) -> ToolSchema: ... async def call(self, tool_call: ToolCall) -> str: ...
Schemas
Pydantic models forming the data contract between agents, tools, and the platform.
Input to every agent invocation.
| Field | Type | Default | Description |
|---|---|---|---|
message | str | required | The user's message |
history | list[Message] | [] | Conversation history |
tool_schemas | list[ToolSchema] | [] | Caller-supplied tool schemas |
metadata | dict[str, Any] | {} | session_id key used by Redis memory |
A single chunk yielded by an agent's run().
| Field | Type | Default | Description |
|---|---|---|---|
content | str | None | None | Text content of this chunk |
tool_calls | list[ToolCall] | [] | Tool calls the agent wants to make |
done | bool | False | Whether this is the final chunk |
metadata | dict[str, Any] | {} | Arbitrary output metadata |
@tool Decorator
Turn any function into a ToolProtocol with one line. Schema auto-generated from type hints and docstring.
Returns a ToolWrapper. Works with sync and async functions.
pythonfrom genfleet.sdk import tool # Bare — schema from type hints + docstring @tool async def search(query: str, limit: int = 10) -> str: """Searches the web for a query.""" return f"Results for {query}" # With explicit name / description @tool(name="add", description="Adds two numbers") def add_numbers(a: float, b: float) -> str: return str(a + b)
Type mappings
| Python | JSON Schema |
|---|---|
str | {"type": "string"} |
int | {"type": "integer"} |
float | {"type": "number"} |
bool | {"type": "boolean"} |
list[str] | {"type": "array", "items": {"type": "string"}} |
dict | {"type": "object"} |
ToolWrapper
The object returned by @tool. Satisfies ToolProtocol — call directly or via a ToolCall.
Wraps any sync or async callable with a JSON schema and ToolProtocol interface.
pythonfrom genfleet.sdk import tool, ToolCall @tool def greet(name: str) -> str: """Says hello.""" return f"Hello, {name}!" # Via ToolCall (how the platform dispatches) result = await greet.call(ToolCall(id="1", name="greet", arguments={"name": "World"})) # Direct call result = await greet(name="World")
A2A Server
Serve any AgentProtocol as an A2A-compliant HTTP endpoint. Requires [serve] extra.
| Method | Path | Description |
|---|---|---|
GET | /.well-known/agent-card.json | Agent card |
POST | / | JSON-RPC 2.0, A2A v1.0 (SendMessage, SendStreamingMessage; the 0.x tasks/send and tasks/sendSubscribe still work for one minor release) |
create_app()
Returns a FastAPI app for custom ASGI deployments.
pythonfrom genfleet.sdk import Agent from genfleet.sdk.serve import create_app app = create_app( Agent(role="You are helpful.", model={"model": "openai/gpt-4o-mini", "api_key": "sk-..."}), name="my-agent", ) # uvicorn myfile:app --port 8000
serve()
Blocking one-liner. Calls uvicorn internally. Best for scripts and local dev.
| Parameter | Type | Default | Description |
|---|---|---|---|
agent | AgentProtocol | required | The agent to serve |
name | str | "agent" | Agent name |
description | str | "" | Agent description |
host | str | "0.0.0.0" | Bind address |
port | int | 8000 | Bind port |
Example: OpenAI Agent
Minimal Agent with a tool using an OpenAI model.
openai_agent.pyimport os from genfleet.sdk import Agent from genfleet.sdk.serve import serve def get_weather(city: str) -> str: """Gets the current weather for a city.""" return f"72°F and sunny in {city}" agent = Agent( role="You are a helpful assistant.", model={"model": "openai/gpt-4o-mini", "api_key": os.environ["OPENAI_API_KEY"]}, tools=[get_weather], ) serve(agent, name="openai-agent", port=8000)
Example: Anthropic Agent
Claude-powered agent — provider auto-detected from the claude- prefix.
anthropic_agent.pyimport os from genfleet.sdk import Agent from genfleet.sdk.serve import serve def lookup(topic: str) -> str: """Looks up a fact in the knowledge base.""" return f"Here's what we know about {topic}: it's fascinating." agent = Agent( role="You are a helpful assistant.", model={"model": "anthropic/claude-sonnet-4-6", "api_key": os.environ["ANTHROPIC_API_KEY"]}, tools=[lookup], ) serve(agent, name="anthropic-agent", port=8002)
Example: Session Memory
Persistent multi-turn conversations backed by Redis.
memory_agent.pyimport os from genfleet.sdk import Agent from genfleet.sdk.serve import serve agent = Agent( role="You are a friendly assistant with a long memory.", model={"model": "openai/gpt-4o-mini", "api_key": os.environ["OPENAI_API_KEY"]}, memory={ "type": "redis", "connection": os.environ.get("REDIS_URL", "redis://localhost:6379/0"), "password": os.environ.get("REDIS_PASSWORD"), }, ) serve(agent, name="memory-agent", port=8000)
shell — two-turn session test# Turn 1 curl -X POST http://localhost:8000 \ -d '{"jsonrpc":"2.0","id":1,"method":"SendMessage","params":{"message":{"messageId":"m1","role":"ROLE_USER","contextId":"demo","parts":[{"text":"My name is Alice"}]}}}' # Turn 2 — agent recalls the name curl -X POST http://localhost:8000 \ -d '{"jsonrpc":"2.0","id":2,"method":"SendMessage","params":{"message":{"messageId":"m2","role":"ROLE_USER","contextId":"demo","parts":[{"text":"What is my name?"}]}}}'
Example: MCP Server
Connect to an MCP server. Tool schemas are fetched on first call and merged with local tools.
mcp_agent.pyimport os from genfleet.sdk import Agent from genfleet.sdk.serve import serve def read_file(path: str) -> str: """Reads a local file.""" with open(path) as f: return f.read() agent = Agent( role="You are a helpful file assistant.", model={"model": "openai/gpt-4o-mini", "api_key": os.environ["OPENAI_API_KEY"]}, tools=[read_file], # local tools mcps=[{ # MCP tools — discovered on first run() "type": "stdio", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"], }], ) serve(agent, name="mcp-agent", port=8000)
Example: LangChain Agent
LangChain manages its own LLM calls — implement AgentProtocol directly.
langchain_agent.pyfrom langchain_core.messages import HumanMessage, AIMessage, SystemMessage from langchain_core.tools import tool as lc_tool from langchain_openai import ChatOpenAI from genfleet.sdk import AgentInput, AgentOutput from genfleet.sdk.serve import serve @lc_tool def calculator(expression: str) -> str: """Evaluates a math expression.""" return str(eval(expression)) llm = ChatOpenAI(model="gpt-4o-mini").bind_tools([calculator]) class LangChainAgent: async def run(self, input: AgentInput): messages = [SystemMessage(content="You are a helpful assistant.")] for msg in input.history: messages.append( HumanMessage(content=msg.content) if msg.role == "user" else AIMessage(content=msg.content or "") ) messages.append(HumanMessage(content=input.message)) response = await llm.ainvoke(messages) if response.tool_calls: yield AgentOutput( tool_calls=[{"id": tc["id"], "name": tc["name"], "arguments": tc["args"]} for tc in response.tool_calls], done=False, ) else: yield AgentOutput(content=response.content, done=True) serve(LangChainAgent(), name="langchain-agent", port=8003)
Example: CrewAI Agent
CrewAI manages its own orchestration — implement AgentProtocol directly and wrap the blocking call.
crewai_agent.pyimport asyncio from crewai import Agent as CrewAgent, Crew, Task from genfleet.sdk import AgentInput, AgentOutput from genfleet.sdk.serve import serve researcher = CrewAgent( role="Senior Research Analyst", goal="Provide thorough research on any topic", backstory="You are an experienced research analyst.", verbose=False, ) class CrewAIAgent: async def run(self, input: AgentInput): task = Task(description=input.message, expected_output="Clear analysis", agent=researcher) crew = Crew(agents=[researcher], tasks=[task], verbose=False) # kickoff() is synchronous — wrap to avoid blocking the event loop result = await asyncio.to_thread(crew.kickoff) yield AgentOutput(content=str(result), done=True) serve(CrewAIAgent(), name="crewai-agent", port=8004)
Publish on Genfleet (private beta)
From an empty directory to chatting with your own agent on the platform: install, write an agent with a tool, test it locally, push it, run it in your workspace, chat. Every command here was checked against the SDK and the gf CLI on 2026-09-30.
$GENFLEET_API), read access to the genfleet/cli repository, and your account from the invitation. Signing up makes you the owner of your own workspace, and that is all gf agents push needs: you need no platform role.
1. Install
Python 3.12 or newer and uv. The SDK and the CLI are installed from GitHub; neither is on PyPI yet.
shell# the gf CLI (log in, push agents) uv tool install git+https://github.com/genfleet/cli gf --help export GENFLEET_API=https://… # the platform URL from your invitation
2. Write an agent with a tool
An agent is a directory with a Python package inside it. The manifest, genfleet.toml, sits inside the package, next to agent.py:
layouthello-agent/ pyproject.toml hello_agent/ __init__.py genfleet.toml # manifest: name, kind, entry agent.py # build_agent() factory + the tool __main__.py # serve it locally tests/ test_agent.py
hello-agent/pyproject.toml[project] name = "hello-agent" version = "0.1.0" requires-python = ">=3.12" dependencies = ["genfleet-sdk[openai,serve] @ git+https://github.com/genfleet/sdk@dev"] [dependency-groups] dev = ["pytest"] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.metadata] allow-direct-references = true [tool.hatch.build.targets.wheel] packages = ["hello_agent"]
hello-agent/hello_agent/genfleet.toml[agent] name = "hello-agent" kind = "code" description = "Says hello and counts words." entry = "hello_agent.agent:build_agent"
hello-agent/hello_agent/agent.pyimport os from genfleet.sdk import Agent, tool # `openrouter/` is the SDK's OpenRouter prefix: one key, any vendor's model. # A `:free` model costs nothing; free ids rotate, so swap in another `:free` # model that supports tool calling if this one stops resolving. MODEL = "openrouter/nvidia/nemotron-3.5-lightning:free" @tool(description="Counts the words in a piece of text.") def count_words(text: str) -> str: return str(len(text.split())) def build_agent(api_key: str | None = None) -> Agent: # A factory, not a module-level Agent(): the package stays importable # without a key, so tests can build it with a dummy one. key = api_key or os.environ.get("OPENROUTER_API_KEY") if not key: raise RuntimeError("Set OPENROUTER_API_KEY (on the platform: a secret, see step 5).") return Agent( role="You are a friendly assistant. Use count_words when asked to count words.", model={"model": MODEL, "api_key": key}, tools=[count_words], )
hello-agent/hello_agent/__init__.py and __main__.py# __init__.py from .agent import build_agent __all__ = ["build_agent"] # __main__.py from genfleet.sdk.serve import serve from .agent import build_agent if __name__ == "__main__": serve(build_agent(), name="hello-agent", port=8000)
openai, anthropic, gemini and serve extras) and the standard library — nothing else. The dependencies in your pyproject.toml are for running locally; they are not installed on the platform. Keep tools as plain Python in your package, as above.
3. Test it locally
A test that needs no network and no key:
hello-agent/tests/test_agent.pyimport asyncio from genfleet.sdk import ToolCall from hello_agent.agent import build_agent, count_words def test_builds_without_a_real_key(): assert build_agent(api_key="test") is not None def test_count_words(): call = ToolCall(id="1", name="count_words", arguments={"text": "one two three"}) assert asyncio.run(count_words.call(call)) == "3"
shellcd hello-agent uv sync uv run pytest -q # 2 passed export OPENROUTER_API_KEY=sk-or-v1-… # a free OpenRouter key works uv run python -m hello_agent # A2A server on :8000
In another shell:
shellcurl -s localhost:8000/.well-known/agent-card.json curl -s -X POST localhost:8000 -H 'Content-Type: application/json' -H 'A2A-Version: 1.0' -d '{"jsonrpc":"2.0","id":1,"method":"SendMessage","params":{"message":{"messageId":"m1","role":"ROLE_USER","parts":[{"text":"How many words are in: the quick brown fox?"}]}}}'
4. Log in and push
gf agents push takes the agent directory (the one with pyproject.toml), finds the manifest one level in, archives the directory deterministically and uploads it. It prints the archive's digest; the platform stores the same bytes under the same digest, and the CLI refuses a mismatch.
shellgf login --api "$GENFLEET_API" # prompts for email and password gf whoami # the account and workspace you are acting as gf agents digest hello-agent # optional: the digest, computed offline gf agents push hello-agent # ... stored sha256:<64 hex>
Pushing stores the code; it does not create an agent yet. It prints the gf agents create command to run next. You can also do both in one step (next section).
5. Run it in your workspace
First the model key. In the web app, open Secrets and create a secret named OPENROUTER_API_KEY. The agent only receives it because its spec references it (below); a secret nothing references reaches no agent.
Then create the agent from the pushed code, reading that secret:
shellgf agents create sha256:<the digest from gf agents push> \ --name hello-agent --secret OPENROUTER_API_KEY # or push, create and start it in one go: gf agents push hello-agent --create --secret OPENROUTER_API_KEY --activate
--secret OPENROUTER_API_KEY passes a reference, not your key. The agent's spec gets "env": {"OPENROUTER_API_KEY": "$OPENROUTER_API_KEY"}, and the platform replaces it with the value of your secret of that name each time the agent starts. The key never passes through gf. With --create, the name and description come from genfleet.toml. Use --name to override the name.
count_words tool above is plain Python inside your package, so it runs as soon as the agent does. A tool your manifest names under [tools] (for example "@acme/jira" = "1.x") is different. It is pinned when the agent is submitted, and it is mounted into the sandbox only after a Genfleet reviewer approves the submission. Until then the agent runs without it. For the beta, keep tools in your package.
[agent], for example egress = ["api.github.com", "*.googleapis.com"]. A tool declares its own under [tool]. Each entry is a hostname, optionally starting with *. and optionally ending with :port (443 by default); IP addresses are refused. For now the proxy only records where each agent goes. Once enforcement is on, it refuses hosts that no list allows.
6. Chat with it
Start the agent's sandbox (skip this if you passed --activate), then send it a message:
shellgf agents activate hello-agent gf agents chat hello-agent "How many words are in: the quick brown fox?"
Or use the web app. Open Agents, find hello-agent and choose Activate. Then open Chat, pick hello-agent and ask it to count some words. Either way, the answer comes from your count_words tool.
Changed the code? Push again and create the agent under a new name (or delete the old one first). To offer it to other workspaces, submit it to the marketplace from the agent's page; a Genfleet reviewer approves it before it is listed.
When something goes wrong
gf agents pushorcreateanswers 403: your role in the workspace is member. Pushing and creating agents need owner, admin or developer. Ask the workspace owner.gf agents createprints “saved but not loaded”: the agent row exists, but the engine could not load it. The message says why. Fix the cause, delete the agent in the web app, and create it again.- The agent will not activate — check that the secret named in
spec.envexists, and that your code imports nothing beyond the SDK and the standard library (step 2). - An error shows an error id or trace id — include it when you ask for help; it is how we find the failure in our logs.
Where to get help
Reply to your invitation email, or contact the person who invited you. SDK bugs and questions: github.com/genfleet/sdk/issues (public, so never paste keys or private code there).
All Exports
Everything exported from genfleet.sdk.
pythonfrom genfleet.sdk import ( # Core Agent, # Main class — declare and run agents AgentProtocol, # Protocol — implement run() for custom integrations ToolProtocol, # Protocol — implement schema() + call() AgentFactory, # Type alias — callable returning AgentProtocol # Schemas AgentInput, # Model — agent invocation input AgentOutput, # Model — single output chunk Message, # Model — conversation history entry ToolCall, # Model — tool invocation request ToolSchema, # Model — tool JSON schema TokenUsage, # Model — token counts (input, output, total) AuditEvent, # Model — structured audit event # Config TypedDicts ModelConfig, # Provider + credentials MemoryConfig, # Memory config — platform store or Redis MCPConfig, # MCP server connection config AuditConfig, # Audit backend config # Tools ToolWrapper, # Class — wraps callable as ToolProtocol tool, # Decorator — function → ToolWrapper ) # A2A serve module (requires [serve] extra) from genfleet.sdk.serve import create_app, serve