genfleet-sdk
The developer SDK for the Genfleet agent marketplace platform.
Declare an agent with Agent() — give it a role, model, tools, memory, or MCP connections — and serve it over A2A. The SDK handles provider selection, tool dispatch, session memory, and streaming automatically.
Install
pip install "genfleet-sdk @ git+https://github.com/genfleet/sdk@dev" # core (pydantic only)
pip install "genfleet-sdk[openai,serve] @ git+https://github.com/genfleet/sdk@dev" # OpenAI + A2A server
pip install "genfleet-sdk[anthropic,serve] @ git+https://github.com/genfleet/sdk@dev" # Anthropic + A2A server
pip install "genfleet-sdk[all] @ git+https://github.com/genfleet/sdk@dev" # everything
Installed from GitHub rather than PyPI: the package is not published yet, so
the git reference is the supported way to get it. Pin a tag or commit instead
of @dev if you need a fixed version.
Requires Python 3.12+.
Agent Skill
Install the genfleet-sdk skill so your coding agent understands the SDK and can help you build agents:
npx skills add genfleet/sdk
Your coding agent will automatically use it when working with Agent(), AgentProtocol, @tool, serve(), and all framework integration patterns.
Quickstart
import 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)
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?"}]}}}'
Publish on Genfleet (private beta)
Invited to the beta? The step-by-step guide — write an agent with a tool,
test it locally, gf agents push it, run it in your workspace and chat with
it — is the Publish on Genfleet section of the docs:
docs/index.html#marketplace.
Agent Constructor
Agent(
role: str, # system prompt / persona
model: ModelConfig, # provider + credentials
tools: list[Callable] = [], # plain Python functions — auto-wrapped
mcps: list[MCPConfig] = [], # MCP server connections
memory: MemoryConfig | "platform" | None = None, # episodic memory (see below)
context: str | None = None, # extra context appended to system prompt
audit: AuditConfig | None = None, # structured audit logging
)
ModelConfig — explicit provider/model-name format
{"model": "openai/gpt-4o-mini", "api_key": "sk-..."} # OpenAI
{"model": "anthropic/claude-sonnet-4-6", "api_key": "sk-ant-..."} # Anthropic
{"model": "gemini/gemini-1.5-pro", "api_key": "..."} # Gemini
{"model": "openrouter/google/gemini-2.5-flash", "api_key": "sk-or-..."} # any vendor via OpenRouter
{"model": "openai/deepseek-chat", "api_key": "sk-...", "base_url": "https://api.deepseek.com"} # any OpenAI-compatible
Model clients — genfleet.model_clients
An agent's model calls go through a model client. The direct providers above
are the default. A package can register its own under the entry-point group
genfleet.model_clients: a factory (ModelConfig) -> ModelClient | None that
returns None when it doesn't apply in the current environment. Installed
factories are tried in name order and the first active one is used (the SDK
logs which). An explicit Agent(model_client=...) wins over both, which is how
tests use genfleet.sdk.models.FakeModelClient.
With a model client installed, model may be a plain id (no provider prefix)
and api_key may be left out. ModelConfig["options"] is passed to the client
unchanged; the SDK never reads it. During a turn,
genfleet.sdk.turn.current_turn() returns the turn's AgentInput.metadata.
Memory
Memory is episodic: one thread of messages per session_id, loaded before a
turn and appended to after it.
- On the platform you need not name a backend. A hosted agent is given
GENFLEET_MEMORY_URL/GENFLEET_MEMORY_TOKENby its sandbox and uses the platform's store, scoped to that agent instance.memory="platform"says so explicitly and fails fast outside a sandbox.agent.memory.search(subject, query)andagent.memory.purge(subject)are available on it. - Anywhere else pass your own Redis, or nothing for a stateless agent.
- Both sandbox variables travel together. With only
GENFLEET_MEMORY_URLset, the agent raises at construction; with onlyGENFLEET_MEMORY_TOKEN, there is nothing to dial, so it runs stateless and logs a warning. - A retried turn is appended twice unless the caller names it:
turn_id,request_idormessage_idin the request metadata is passed to the store as the turn's idempotency key.
With Redis memory
agent = Agent(
role="You are a helpful assistant.",
model={"model": "openai/gpt-4o-mini", "api_key": os.environ["OPENAI_API_KEY"]},
memory={
"type": "redis",
"connection": "redis://localhost:6379",
},
)
Send the same contextId on each message to persist history across turns (it becomes the agent's session_id):
curl -X POST http://localhost:8000 -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"}]}}}'
With MCP server
agent = Agent(
role="You are a file assistant.",
model={"model": "openai/gpt-4o-mini", "api_key": os.environ["OPENAI_API_KEY"]},
mcps=[{
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
}],
)
serve(...) / create_app(...) options for an agent you host yourself: public_url="https://…/" fixes the URL the agent card advertises (otherwise it follows the request's Host and is not cached), and accept_history=True lets a trusted caller send prior turns in metadata["genfleet.history"] (off by default).
With audit logging
agent = Agent(
role="You are a helpful assistant.",
model={"model": "openai/gpt-4o-mini", "api_key": os.environ["OPENAI_API_KEY"]},
audit={
"backend": "console", # "console", "file", or "callback"
"agent_name": "my-agent",
},
)
Console backend — emits structured JSON to the genfleet.audit logger:
audit={"backend": "console"}
File backend — appends JSONL to a file:
audit={"backend": "file", "file_path": "audit.jsonl"}
Callback backend — calls your function (sync or async) for each event:
async def my_handler(event):
print(event.event_type, event.data)
audit={"backend": "callback", "callback": my_handler}
AuditConfig options:
| 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 | Handler function |
max_content_length |
int |
500 |
Truncation limit (0 = unlimited) |
events |
list[str] |
all | Filter which event types to emit |
agent_name |
str |
None |
Name tag included in every event |
Audit events emitted:
| 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 and included in llm.response and invocation.end events.
What's in the SDK
| Export | Type | Description |
|---|---|---|
Agent |
Class | Main developer-facing class — wires provider, tools, memory, MCP, audit |
AgentProtocol |
Protocol | Universal agent contract — implement run() |
ToolProtocol |
Protocol | Tool contract — implement schema() + call() |
AgentInput |
Model | Input to every agent invocation |
AgentOutput |
Model | Output chunk yielded by agents |
Message |
Model | Conversation history entry |
ToolCall |
Model | Tool invocation request |
ToolSchema |
Model | Tool JSON Schema description |
TokenUsage |
Model | Token counts (input, output, total) |
TOOL_EVENT_KEY |
Constant | AgentOutput.metadata key on the outputs Agent yields to report a tool call and its result |
AuditEvent |
Model | Structured audit event |
ModelConfig |
TypedDict | Provider + credentials config |
MemoryConfig |
TypedDict | Memory config — {"type": "redis", ...} or {"type": "platform"} |
AuditConfig |
TypedDict | Audit backend config |
MCPConfig |
TypedDict | MCP server connection config |
ToolWrapper |
Class | Wraps any callable as a ToolProtocol |
@tool |
Decorator | Function → ToolWrapper with auto-generated schema |
@tool decorator
Plain functions passed to tools= are auto-wrapped. Use @tool when you want an explicit name or description:
from genfleet.sdk import tool, ToolCall
@tool(name="add", description="Adds two numbers")
def add(a: float, b: float) -> str:
return str(a + b)
result = await add.call(ToolCall(id="1", name="add", arguments={"a": 3, "b": 4}))
# "7.0"
Operators and customers
On a hosted agent the platform tells each turn who is writing, as AgentInput.metadata["genfleet.caller"] (CALLER_METADATA_KEY, from genfleet.sdk.caller): role ("operator" or "customer"), id (the verified sender), channel, private (a one-to-one chat), legacy_tools, and name. name is the sender's own display name and is untrusted; don't base decisions on it. Use caller_of(input.metadata) to read it.
Every tool has an audience: who it is offered to. Mark it on the tool:
@tool(audiences=["operator", "customer"])
def order_status(order_id: str) -> str: ...
@tool(audiences=["customer"]) # customers only: staff shouldn't trigger it
def start_return(order_id: str) -> str: ...
An unmarked tool is ["operator"]. An operator in a private chat gets the tools whose audience includes operator. A customer, or an operator writing in a group, gets those whose audience includes customer. So a ["customer"] tool is hidden from staff in a private chat. A call to a tool that wasn't offered is refused as "not found". For MCP servers, the config's customer_safe_tools: [names] (the 0.17 form) marks tools for both audiences; there is no customer-only form in the config, but the platform's setting can make one by name.
Your marking is the default, and the platform's setting wins. The tenant owner sets each tool's audience in the dashboard. The platform sends it as metadata["genfleet.tool_audiences"] (TOOL_AUDIENCES_METADATA_KEY), keyed by tool name, or by manifest slug for tools from load_tools(). Each tool it returns carries its slug, on a fresh proxy that behaves like the original. That setting replaces your marking, and can widen it as well as narrow it. An entry the SDK can't read is offered to no one. Agent applies this to every tool it holds: its own functions, load_tools() tools, remote tools passed in as tools=, and MCP tools. Tools the engine keeps itself are filtered by the engine. Read the effective rule with may_offer(caller, audiences).
customer_safe=True (0.17) still works and means both audiences, but it is deprecated and removed in 1.0, as are may_use and Caller.full_toolset.
Sensitive tools. Mark a tool whose call needs a workspace owner's approval:
@tool(audiences=["operator", "customer"], sensitive=True)
def issue_refund(order_id: str, amount: float) -> str: ...
When the model calls it, it doesn't run. The model is told the action awaits approval, and the platform shows the owner or admin the exact call to approve. Once approved, a follow-up turn runs that one call, exactly once, and the model tells the user the outcome. The engine signs the approved call with a key only this sandbox knows (GENFLEET_APPROVAL_KEY), so another agent or a direct caller can't forge one. On a call from another agent, a sensitive tool is refused. The owner can mark or unmark any tool in the dashboard, and that setting wins.
An agent the platform did not spawn (a local serve, a test) has no roles: a turn without the caller key gets every tool. In a platform sandbox (GENFLEET_HOSTED set; on older engines, GENFLEET_A2A_TOKEN) a missing caller gets the narrowest set, as a malformed one always does. legacy_tools gets every tool. An MCP tool whose name is already taken by another tool is skipped.
Declare who the agent serves in genfleet.toml:
[agent]
name = "support"
audiences = ["operator", "customer"] # default when absent: operators only
AgentProtocol — direct implementation
For framework integrations (LangChain, CrewAI) that manage their own LLM calls, implement AgentProtocol directly:
from genfleet.sdk import AgentInput, AgentOutput, AgentProtocol
from genfleet.sdk.serve import serve
class MyAgent:
async def run(self, input: AgentInput):
yield AgentOutput(content=f"Got: {input.message}", done=True)
assert isinstance(MyAgent(), AgentProtocol) # passes
serve(MyAgent(), name="my-agent", port=8000)
Package extras
| Extra | Installs | Use for |
|---|---|---|
openai |
openai>=1.0 |
OpenAI + any OpenAI-compatible endpoint |
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 |
Examples
See examples/ for complete runnable agents:
| File | Description |
|---|---|
| echo_agent.py | Minimal A2A agent (no LLM) |
| streaming_agent.py | SSE streaming |
| tool_agent.py | @tool decorator demo |
| openai_agent.py | OpenAI GPT-4o-mini with tools |
| anthropic_agent.py | Anthropic Claude |
| langchain_agent.py | LangChain via AgentProtocol |
| crewai_agent.py | CrewAI via AgentProtocol |
| agent_with_tools.py | Multi-tool agent |
| memory_agent.py | Redis-backed session memory |
| mcp_agent.py | MCP filesystem server |
| rag_agent.py | RAG with LightRAG |
Requirements
- Python 3.12+
pydantic >= 2.7- Provider extras:
[openai],[anthropic],[gemini] [serve]extra:fastapi,uvicorn,sse-starlette[memory]extra:redis[mcp]extra:mcp
License
Apache 2.0 — see LICENSE for details.
Metadata
Release files for genfleet-sdk 0.19.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| genfleet_sdk-0.19.0.tar.gz | 284.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| genfleet_sdk-0.19.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 362.4 kB
Release files / genfleet_sdk-0.19.0.tar.gz
| Download URL | genfleet_sdk-0.19.0.tar.gz |
|---|---|
| Size | 284.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
34dadb146b319406ac9d236816f451f60283b54d334329492ee56e8da2fb94bb
|
|
BLAKE2b-256 checksum How to use checksums |
62dae80cadde088fd2c03e65ffa4b1b4e4f1657f386d62654bc45664a7d4bf05
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logRelease files / genfleet_sdk-0.19.0-py3-none-any.whl
| Download URL | genfleet_sdk-0.19.0-py3-none-any.whl |
|---|---|
| Size | 77.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
01e0b0c8c92fbeceb0fbcc8f815886e02873a3ae1d961181ccca5cf16933d6bd
|
|
BLAKE2b-256 checksum How to use checksums |
f31f9c4c8969d897ca91384bc20bfd0e2c924f1602cb6532776dabaa0dfb3ca5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency log