Python SDK
Everything you need to build, run, and refine agents from Python.
Installation
pip install 'flymyai==1.2.0rc5'
This release-candidate page is bound to Python SDK 1.2.0rc5, which matches
the MCP resource-scope contract and Agents MCP 0.7.0-rc.8. Requires Python
3.8+. The agents module ships inside the flymyai package. Capability-detect
an older installed package before using candidate namespaces.
30-second example
from uuid import uuid4
from flymyai import AgentClient
client = AgentClient(api_key="fly-***")
# 1. Add a tool
tool = client.tools.create(mcp_tool="tavily")
# 2. Create an agent
agent = client.agents.create(
name="Web Researcher",
goal="Search the web for the latest AI agent frameworks and return a concise summary with sources.",
tools=[tool.id],
)
# 3. Run & wait
run = client.runs.create(
agent_id=agent.id,
idempotency_key=f"web-research-{uuid4()}",
)
result = client.runs.wait(run.id)
print(result.output)
# To make the agent reusable, add an input_schema, use {{ placeholders }} in the
# goal, and pass values plus a new caller-owned key per logical run.
# See "Inputs, Outputs & Variables" for details.
Client
- Sync
- Async
from flymyai import AgentClient
client = AgentClient(
api_key="fly-***", # or set FLYMYAI_API_KEY env var
base_url="...", # default: https://backend.flymy.ai
timeout=60.0, # request timeout in seconds
)
from uuid import uuid4
from flymyai import AsyncAgentClient
async with AsyncAgentClient(api_key="fly-***") as client:
agent = await client.agents.create(name="Bot", goal="Help users")
run = await client.runs.create(
agent_id=agent.id,
idempotency_key=f"bot-run-{uuid4()}",
)
result = await client.runs.wait(run.id)
| Env variable | Description | Default |
|---|---|---|
FLYMYAI_API_KEY | API key (used when api_key is omitted) | - |
FLYMYAI_AGENTS_BASE_URL | Base URL override for the agents API | https://backend.flymy.ai |
The agents API (AgentClient) and the model-inference API (FlyMyAI, flymyai.run) live on different hosts and have separate env vars:
| Client | Default base URL | Env var override |
|---|---|---|
AgentClient (this page) | https://backend.flymy.ai | FLYMYAI_AGENTS_BASE_URL |
FlyMyAI (model inference) | https://api.flymy.ai/ | FLYMYAI_DSN |
Setting one does not affect the other, so you can point each client at staging independently.
The candidate client exposes eight resource namespaces. AsyncAgentClient
provides the same namespaces with awaited methods.
| Namespace | What it does |
|---|---|
client.agents | Create, list, update, delete, and run agents |
client.runs | Start runs, poll for results, stream events, follow up |
client.tools | Browse the tool catalog, add and configure MCP tools |
client.mcp_resource_sets | Create revisioned sets of exact MCP connections and slots |
client.agent_groups | Share resource sets with flat agent groups |
client.compilations | Freeze and run accepted instructions |
client.versions | Read immutable versions materialized from accepted freezes |
client.deployments | Publish one stable endpoint and run it for isolated product customers |
client.agents
Create
agent = client.agents.create(
name="Lead Profiler",
goal="""Research {{ name }} at {{ company }}.
Return background, recent initiatives, and recommended outreach angle.""",
tools=[web_search_id, browse_id], # tool IDs (integers)
input_schema={
"type": "object",
"properties": {
"name": {"type": "string"},
"company": {"type": "string"},
},
"required": ["name", "company"],
},
input_description="Name and company of the lead to research.",
output_schema={
"type": "object",
"properties": {
"background": {"type": "string"},
"outreach_angle": {"type": "string"},
},
"required": ["background", "outreach_angle"],
},
output_description="A short background blurb and a recommended outreach angle.",
)
print(agent.id) # UUID string
print(agent.goal) # alias for user_prompt
print(agent.status) # "draft"
| Parameter | Type | Required | Description |
|---|---|---|---|
name | str | Yes | Human-readable name |
goal | str | Yes | Instructions (supports Handlebars templating) |
tools | list[int] | No | Tool IDs to attach |
mcp_servers | list[int] | No | Custom MCP server IDs to attach |
input_schema | dict | No | JSON Schema for runtime variables (required if goal has placeholders) |
input_description | str | No | Plain-text description of what the user provides; authoritative scope at freeze time |
output_schema | dict | No | JSON Schema the agent must produce as its final result |
output_description | str | No | Plain-text description of the result; pairs with output_schema to bound the canonical pipeline |
status | str | No | draft (default) or active |
List / Get / Update / Delete
list(page_size=100) follows the server's bounded cursor pages and returns at
most 10,000 agents. The page size must be between 1 and 100.
# List all agents
agents = client.agents.list()
# Get with full details (nested tool objects)
agent = client.agents.get("a1b2c3d4-...")
# Partial update - only send the fields you want to change
agent = client.agents.update("a1b2c3d4-...",
goal="Updated instructions.",
)
# Soft-delete (archive)
client.agents.delete("a1b2c3d4-...")
Run
Start the agent loop. Returns immediately - the agent executes asynchronously on the server.
run = client.agents.run(
"a1b2c3d4-...",
idempotency_key="lead-profile-<uuid>",
)
print(run.id) # execution ID (string slug, e.g. "won-gsfr-mxp")
print(run.status) # "pending" or "running"
client.runs.create(agent_id=...) is an alias for client.agents.run(...). Use whichever reads better in your code.
client.runs
Create a run
run = client.runs.create(
agent_id=agent.id,
idempotency_key="lead-profile-<uuid>",
)
# With an input_schema, pass values for the goal's {{ placeholders }}:
run = client.runs.create(
agent_id=agent.id,
variables={"topic": "AI agents", "depth": "deep"},
idempotency_key="topic-research-<uuid>",
)
variables are validated against the agent's input_schema (required when one is set). Without an input_schema they are ignored and placeholders are sent verbatim.
Every live-agent and frozen-compilation run requires a caller-owned, nonblank
printable ASCII idempotency_key of at most 255 characters with no leading or
trailing spaces. Reserve it before dispatch and
reuse it only to replay the exact same request. The SDK does not generate a
fallback.
Get run details
run = client.runs.get("won-gsfr-mxp")
print(run.status) # "completed"
print(run.output) # the agent's result (dict)
print(run.error) # None or error message
for log in run.logs:
print(f"[{log.type}] {log.message}")
Wait for completion
Polls the server until the run finishes. Returns the final RunDetail.
result = client.runs.wait(run.id, timeout=300, poll_interval=2.0)
if result.status == "completed":
print(result.output)
else:
print(f"Run ended with status: {result.status}")
print(f"Error: {result.error}")
Raises TimeoutError if the run doesn't finish in time.
Stream events
Watch execution step-by-step. Yields ExecutionLog objects as they appear.
run = client.runs.create(
agent_id=agent.id,
idempotency_key="stream-events-<uuid>",
)
for event in client.runs.stream_events(run.id, timeout=600):
if event.type == "tool_called":
print(f" Tool: {event.message}")
elif event.type == "tool_call_exception":
print(f" Error: {event.message}")
else:
print(f" [{event.type}] {event.message}")
Event types:
| Type | Meaning |
|---|---|
declared_functions | Agent declared the tools it will use |
tool_called | A tool was invoked |
tool_call_exception | A tool call failed |
task_cancelled | The run was cancelled |
Cancel
client.runs.cancel("won-gsfr-mxp")
Follow up
Append a user message and restart the agent loop on the same execution:
run = client.runs.append_message(run.id, text="Also check their LinkedIn profile.")
result = client.runs.wait(run.id)
client.tools
Tools are MCP integrations available on the FlyMy.AI platform. You configure them once, then attach to any agent.
Browse the catalog
catalog = client.tools.available()
for t in catalog:
print(f"{t.name:20s} {t.type:15s} {t.title}")
# github composio Github
# tavily custom_class Tavily
# telegram-mcp oauth Telegram Mcp
Add and configure a tool
# Add
tool = client.tools.create(mcp_tool="github")
print(tool.is_configured) # False
# Walk through configuration steps
while not tool.is_configured:
step = tool.next_configuration_step
if not step:
break
print(f"Step: {step['description']}")
# → "Provide your GitHub token"
tool = client.tools.provide_config(
tool.id,
user_response={"GITHUB_TOKEN": "ghp_..."},
)
print(tool.is_configured) # True
Set up a saved Browser Use account
The profile belongs to one exact Browser Use connection. FlyMyAI platform
billing works without a Browser Use key; set BROWSER_USE_API_KEY on that
connection only when you want the provider to bill your own account.
Initial profile creation is durable and is not safe to repeat after an unknown provider outcome. Reconcile only the retained exact profile identity:
browser = client.tools.create(mcp_tool="browser_use")
binding = client.tools.create_browser_profile(browser.id, name="My browser")
# Use after an unknown create response. This performs an exact read-only check;
# it never creates or lists provider profiles.
binding = client.tools.reconcile_browser_profile(browser.id)
binding = client.tools.get_browser_profile(browser.id)
The async client exposes the same three awaited methods. Add a second Browser Use connection for a second account instead of replacing the first profile ID. Cookie-domain metadata does not expose cookie values and is not proof that a login remains valid.
Call a tool directly
For custom_class tools, you can invoke actions without running a full agent:
result = client.tools.call(
tool.id,
action="search",
arguments={"query": "FlyMyAI agents"},
idempotency_key="direct-tool-search-<uuid>",
)
print(result)
Browser Use sessions and their files require an existing addressable agent execution. Pass the same execution and exact connection for task creation, polling, follow-ups, downloads and stop:
result = client.tools.call(
browser.id,
action="run_browser_task",
arguments={"task": "Open example.com and tell me the page title"},
execution_id=run.id,
idempotency_key="browser-title-<uuid>",
)
The result uses the same bounded session contract as agent chat, including
separate progress, token and cost fields when the provider reports them.
live_url, live_url_clean, and share_url are bearer-like full-control links
to the active browser, not public replay URLs. Do not log or publish them, and
stop the session to revoke access. Approved durable recordings are returned as
ordered recording_files; singular recording_file is only the index-0
compatibility alias. Omit proxy_country_code for the provider default or pass
explicit None to disable the proxy.
Supplying execution_id adds owner authority and recovery context. It does not
create another chat. Direct REST/MCP and compiled-runtime calls do not currently
create the canonical ToolCall billing row, so use the normal agent-chat path
for platform-billed Browser Use runs.
Other operations
tools = client.tools.list( # bounded cursor traversal
page_size=100,
mcp_tool="gmail", # exact optional filter
alias="support", # exact optional filter
)
tool = client.tools.get(7) # by ID
tool = client.tools.update(7, user_config={"key": "new_value"}) # merge config
client.tools.delete(7) # remove
client.mcp_resource_sets
The simple personal flow still attaches the first default connection by its
numeric tool ID and uses mcp_access_mode="legacy". Create a named resource
set only when the same agent needs multiple exact accounts, shared access, or
explicit policy.
support = client.tools.create(mcp_tool="gmail", alias="support")
sales = client.tools.create(mcp_tool="gmail", alias="sales")
resource_set = client.mcp_resource_sets.create(
name="Inbox operations",
idempotency_key="inbox-operations-create-v1",
management_mode="flymyai",
)
resource_set = client.mcp_resource_sets.replace_members(
resource_set.id,
expected_revision=resource_set.revision,
members=[
{
"resource_type": "user_mcp_tool",
"resource_id": support.public_id,
"slot": "mailbox_pool",
"allowed_actions": ["GMAIL_SEARCH_EMAILS"],
"position": 0,
},
{
"resource_type": "user_mcp_tool",
"resource_id": sales.public_id,
"slot": "mailbox_pool",
"allowed_actions": ["GMAIL_SEARCH_EMAILS"],
"position": 1,
},
],
)
agent = client.agents.update(
agent.id,
available_tools=[],
mcp_resource_set_ids=[resource_set.id],
mcp_access_mode="scoped",
)
An owner may retain at most 25 connection rows for one toolkit, including inactive rows. Creating an already existing alias stays idempotent at the limit. Delete an unused connection row to free capacity.
One slot pools 1-25 exact connections of the same toolkit with the same action
ceiling. Member identity is (resource_type, resource_id, slot), so the same
connection may also appear in another slot with a different ceiling. When an
action has several eligible connections, pass the exact public UUID selected
from the runtime schema as _flymyai_connection. Never choose by alias, email,
toolkit name, or row order.
Metadata updates and complete member replacement require the revision you
loaded. A stale 409 raises McpResourceSetStaleRevisionError; reload and
review instead of overwriting another writer. list() follows compact cursor
pages internally, admitting at most 100 rows per page and printable, nonblank
cursors of at most 1024 Unicode code points.
client.mcp_resource_sets.list() returns McpResourceSetSummary rows with
member_count and no nested members. Use
client.mcp_resource_sets.get(resource_set.id) for one bounded full set or
client.mcp_resource_sets.list_members(resource_set.id) to traverse a large member
collection. The async namespace has the same methods and wire limits.
Both sync and async create() require a caller-owned printable ASCII
idempotency_key of at most 255 characters with no leading or trailing spaces.
Persist the key with the
exact create arguments before dispatch. Reuse it only to replay that identical
request after an uncertain response; changed-body reuse returns 409.
client.agent_groups
Agent groups are flat and share exact resource-set grants. They do not create new connection or agent identities.
group = client.agent_groups.create(
name="Inbox agents",
idempotency_key="inbox-agents-create-v1",
agent_ids=[agent.id],
resource_set_ids=[resource_set.id],
)
Agent-group create uses the same durable key contract. Give each new logical group its own persisted key and never rotate it to recover a lost response.
Supplying assignment lists on update replaces those lists atomically. A child or subagent receives no connector authority from its parent unless it has its own direct grant or group membership.
client.compilations
Once a chat session has done the right thing, you can freeze it: the backend reads the chat, the agent's input_description / output_description, and the schemas, then distills the canonical input -> output pipeline into a Markdown instruction. The frozen instruction can be re-run later with fresh variables - fast, deterministic, no re-exploration.
Freeze and run end-to-end
# 1. The agent is happy with the last run.
run = client.runs.get(run_id)
# 2. Freeze it - returns immediately, then polls until COMPILED.
compilation = client.agents.compile_from_run(run.id, timeout=120)
print(compilation.instruction_md) # the frozen Markdown plan
# 3a. Run without variables (agent has no input_schema):
result_run = client.compilations.run_instruction_and_wait(
compilation.id,
idempotency_key="frozen-no-input-<uuid>",
)
print(result_run.output)
# 3b. Run with variables (agent has an input_schema):
from flymyai import VariablesValidationError
try:
result_run = client.compilations.run_instruction_and_wait(
compilation.id,
variables={"website_url": "https://example.com"},
idempotency_key="frozen-website-score-<uuid>",
)
except VariablesValidationError as err:
print(err.field_errors)
raise
print("Status:", result_run.status)
print("Output:", result_run.output) # shaped by output_schema
print("Score:", result_run.output["score"]) # individual fields
Lower-level building blocks
| Method | What it does |
|---|---|
client.agents.freeze(execution_id) | POST /api/v1/agents/compilations/freeze-instruction/{id}/; returns immediately, status starts at pending |
client.agents.compile_from_run(execution_id, timeout=120) | Freeze + poll until terminal (compiled / failed) |
client.compilations.list() | Every compilation in the workspace |
client.compilations.get(comp_id) | One compilation with instruction_md, status, error |
client.compilations.run_instruction(comp_id, variables=..., idempotency_key=...) | Dispatch a fresh execution from the frozen instruction; returns a new Run immediately |
client.compilations.run_instruction_and_wait(comp_id, variables=..., idempotency_key=..., timeout=600) | Same as above, plus polling until terminal |
client.compilations.wait(comp_id, timeout=120) | Poll an existing compilation until terminal |
Schedule a frozen instruction (cron)
A frozen compilation can re-run automatically on a schedule. Set a standard
5-field cron expression (and an optional IANA timezone) with compilations.update:
# Run every weekday at 09:00 Europe/Tallinn time
client.compilations.update(
compilation.id,
cron_schedule="0 9 * * 1-5",
timezone="Europe/Tallinn",
)
Each scheduled run executes the frozen instruction with the compilation's stored variables and appears as a normal run you can poll like any other. To pause/remove the schedule, clear the cron expression:
client.compilations.update(compilation.id, cron_schedule="")
Notes:
cron_scheduleis a standard 5-field cron expression (minute hour day-of-month month day-of-week), e.g.*/30 * * * *(every 30 minutes) or0 0 * * 0(Sundays at midnight).timezoneis an IANA name (e.g.UTC,America/New_York); defaults toUTC.- The schedule activates only after the compilation is frozen (has an instruction); clearing the cron disables it.
Plain HTTP equivalent (PATCH the compilation with your fly- API key):
curl -X PATCH https://backend.flymy.ai/api/v1/agents/compilations/<compilation_id>/ \
-H "X-API-KEY: $FLYMYAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"cron_schedule": "0 9 * * 1-5", "timezone": "Europe/Tallinn"}'
When scheduled runs fail
When a scheduled run fails, the agent's owner gets an email with the reason and
the link that fixes it: reconnect a connection, top up the balance, or open the
agent. After 3 failed scheduled runs in a row the schedule pauses itself. The cron
is kept, nothing runs, and the compilation (GET /api/v1/agents/compilations/<id>/,
or get_compilation over MCP) reports why:
{
"cron_schedule": "0 9 * * 1-5",
"schedule_active": false,
"schedule_paused": true,
"schedule_paused_reason": "Paused after 3 failed runs in a row. Your FlyMy.AI balance ran out, so the run could not start."
}
Fix the cause, then resume. Setting the cron again resumes the schedule, and so
does schedule_paused: false (the agent page has a Resume button too):
curl -X PATCH https://backend.flymy.ai/api/v1/agents/compilations/<compilation_id>/ \
-H "X-API-KEY: $FLYMYAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"schedule_paused": false}'
{"schedule_paused": true} pauses a schedule by hand. Failed runs from before a
resume no longer count toward the next pause.
Legacy: compile to Python
For workflows where you want a generated Python script instead of a Markdown plan, the older compile endpoint is still available:
compilation = client.compilations.compile(execution_id=run.id)
client.compilations.wait(compilation.id) # poll until compiled
print(compilation.script_code) # generated Python
client.compilations.run(compilation.id) # execute the script
Prefer freeze + run_instruction for new code - it carries the canonical scope from input_description / output_description and runs through the same agent loop with full tool support.
client.versions and client.deployments
An accepted instruction freeze materializes one immutable version. Publish it through one stable deployment ID, then keep product customer identity on your trusted backend:
versions = client.versions.list(agent_id=agent.id)
version = next(v for v in versions if v.source_compilation == compilation.id)
deployment = client.deployments.create(
agent_id=agent.id,
version_id=version.id,
name="Inbox brief",
)
preflight = client.deployments.preflight(deployment.id)
if not preflight.ready:
raise RuntimeError("Deployment is not ready")
deployment = client.deployments.publish(deployment.id)
# Derive this opaque ID from your authenticated server session. Never accept it
# from a model prompt or an unauthenticated browser field.
external_user_id = authenticated_product_user_id
run = client.deployments.run(
deployment.id,
external_user_id=external_user_id,
idempotency_key="customer-inbox-brief-<uuid>",
variables={},
)
FlyMyAI-managed saved bindings omit both resource_set_id and connections.
A customer-managed named mapping sends its stable resource_set_id and
positive resource_set_revision. A one-off mapping sends exact connection
UUIDs keyed by frozen slot. Named and one-off mappings are mutually exclusive,
never expose credentials, and never change the deployment owner's FlyMyAI
billing identity.
Training with patches
Improve your agent iteratively by evaluating output and patching its configuration.
Refine the goal
run = client.runs.create(
agent_id=agent.id,
idempotency_key="refinement-baseline-<uuid>",
)
result = client.runs.wait(run.id)
# Output too verbose? Patch the instructions
client.agents.update(agent.id,
goal="Research {{ topic }}. Be concise: max 3 paragraphs, bullet points preferred.",
)
Automated training loop
for i in range(5):
run = client.runs.create(
agent_id=agent.id,
idempotency_key=f"training-iteration-{i}-<uuid>",
)
result = client.runs.wait(run.id)
score = evaluate(result.output) # your scoring function
if score >= 0.8:
break
See the Training with Patches guide for detailed patterns.
client.artifacts
Needs flymyai 1.2.0rc8 or later: pip install 'flymyai>=1.2.0rc8'.
Frontend artifacts (flymy.artifact.v1): pages, presentations and mini games with immutable
versions. See Frontend Artifacts for sharing, people and clones.
from flymyai.agents import artifact_file, artifact_files_from_directory
made = client.artifacts.create(
name="Pod racer",
files=artifact_files_from_directory("./pod-racer"),
visibility="link",
idempotency_key="pod-racer-create-1",
)
racer = made.artifact
# a new version on top of the latest; the other files are kept
client.artifacts.publish(
racer.id,
base_version=racer.latest_version,
files=[artifact_file("js/app.js", "speed = 2")],
idempotency_key="pod-racer-v2",
)
# people by name: an invitation they accept first; "edit" may publish versions,
# "view" may read and clone
invited = client.artifacts.add_member(racer.id, "teammate@example.com", role="edit")
print(invited.member.status) # pending
# on the teammate's side
for invitation in client.artifacts.invitations().results:
client.artifacts.accept_invitation(invitation.id)
for shared in client.artifacts.list(scope="shared").results:
print(shared.name, shared.role)
# your own copy of an artifact shared with sources
copy = client.artifacts.clone(share_link=racer.share_url, idempotency_key="clone-1")
| Method | What it does |
|---|---|
status() | Your limits |
list(scope="mine" | "shared") | One page of artifacts, most recently changed first |
get(id) / get(share_link=...) | One artifact with its live version and versions |
create(...) / publish(...) | Create, or add a version on top of base_version |
versions, files, read_file, view | Versions, a version's files, one text file, a frame URL |
update(...) / share(...) | Rename or choose the live version / who sees it |
clone(...), lineage(...), history(...) | Your own copy, where it came from, what happened |
members, add_member, remove_member | People it is shared with by name: invite, see who accepted, remove by member id |
invitations, accept_invitation, decline_invitation | Invitations waiting for you, and your answer |
delete(id) | Delete it |
Writes that create something take a caller-owned idempotency_key. A version published on a
stale base_version raises ArtifactStaleBaseVersionError with latest_version. These are the
v1 methods; they stay, and a breaking change would arrive as new methods next to them.
client.projects
Needs flymyai 1.2.0rc8 or later: pip install 'flymyai>=1.2.0rc8'.
Projects (flymy.project.v1): an app applied from one flymy.yaml, a page published on its
own, a fleet (a lead agent and the agents it starts) or a frontend artifact. A plan creates
nothing; create and start apply exactly the plan the user confirmed. See
Projects.
for project in client.projects.list().projects:
print(project.id, project.kind, project.status)
plan = client.projects.plan(name="paint-arena", budget={"per_day_usd": "5"})
print(plan.plan["usd_per_hour"], plan.plan["changes"]) # show the user first
made = client.projects.create(
name="paint-arena",
budget={"per_day_usd": "5"},
plan_token=plan.plan_token,
idempotency_key="paint-arena-create-1",
)
| Method | What it does |
|---|---|
list() | Your apps and pages, then fleets and artifacts, with status, money and errors |
get(project_id) | One project with its pages, agents, storages, money and diagram |
errors(project_id, limit=, before=) | A page of its errors journal, newest first |
templates() | What a new project can start from |
plan(...) / create(...) | Preview a new project and its price / create exactly that plan |
agent(project_id) | The agent in charge of an app or a page, created on first use |
stop(project_id, ...) / start(project_id, ...) | Stop it; start it from a plan you confirm |
A fleet (fleet:<lead agent id>) and an artifact (artifact:<artifact id>) are read with get
and errors; they have no project agent and are not stopped or started.
client.apps
Needs flymyai 1.2.0rc8 or later: pip install 'flymyai>=1.2.0rc8'.
An app's config applied as a release: plan shows what applying a flymy.yaml and the files it
names would create, change or stop and its price, without spending; apply applies exactly that
plan.
from flymyai.agents import artifact_files_from_directory
files = artifact_files_from_directory("./paint-arena") # flymy.yaml at the root
plan = client.apps.plan(files=files)
release = client.apps.apply(
files=files, plan_token=plan.plan_token, idempotency_key="paint-arena-apply-2"
)
print(client.apps.status(release.release).status) # applying, succeeded or failed
| Method | What it does |
|---|---|
plan(files=...) / plan(app=..., overrides=...) | What applying would do and cost, with a plan_token |
apply(..., plan_token=, idempotency_key=) | Apply exactly that plan as a release |
files(app, path=, offset=, limit=) | The applied template, or one page of one file |
status(release_id) | One release: applying, succeeded or failed, with its agents and pages |
Async usage
Every method has an async counterpart:
import asyncio
from uuid import uuid4
from flymyai import AsyncAgentClient
async def main():
async with AsyncAgentClient(api_key="fly-***") as client:
agent = await client.agents.create(
name="Researcher",
goal="Find info about {{ topic }}.",
)
run = await client.runs.create(
agent_id=agent.id,
idempotency_key=f"async-research-{uuid4()}",
)
# Stream events
async for event in client.runs.stream_events(run.id):
print(f"[{event.type}] {event.message}")
result = await client.runs.get(run.id)
print(result.output)
asyncio.run(main())
Complete example
End-to-end: set up tools, create agent, run, stream, follow up, train.
from uuid import uuid4
from flymyai import AgentClient
client = AgentClient(api_key="fly-***")
# ── 1. Set up tools ─────────────────────────────────────────────
catalog = client.tools.available()
print("Available:", [t.name for t in catalog])
tool = client.tools.create(mcp_tool="tavily")
tool = client.tools.provide_config(
tool.id,
user_response={"TAVILY_API_KEY": "tvly-..."},
)
# ── 2. Create agent ─────────────────────────────────────────────
agent = client.agents.create(
name="Research Assistant",
goal="Research {{ topic }} thoroughly. Return a summary with key findings and sources.",
tools=[tool.id],
)
# ── 3. Run & stream ─────────────────────────────────────────────
run = client.runs.create(
agent_id=agent.id,
idempotency_key=f"research-assistant-{uuid4()}",
)
print(f"Run {run.id} started")
for event in client.runs.stream_events(run.id, timeout=600):
print(f" [{event.type}] {event.message}")
# ── 4. Get result ────────────────────────────────────────────────
result = client.runs.get(run.id)
if result.status == "completed":
print("\nOutput:", result.output)
# ── 5. Follow up ────────────────────────────────────────────────
updated = client.runs.append_message(
run.id, text="Now compare this with last year's trends."
)
final = client.runs.wait(updated.id, timeout=600)
print("\nUpdated:", final.output)
Error handling
from flymyai import AgentClient, FlyMyAIAgentError
client = AgentClient(api_key="fly-***")
try:
agent = client.agents.get("nonexistent-uuid")
except FlyMyAIAgentError as e:
print(e.status_code) # 404
print(e.response_body) # {"detail": "Not found."}
| Exception | When |
|---|---|
FlyMyAIAgentError | Any non-2xx API response (has .status_code and .response_body) |
TimeoutError | runs.wait() or runs.stream_events() exceeded timeout |
ValueError | Missing api_key at initialization |
Type reference
Agent
| Field | Type | Description |
|---|---|---|
uuid / id | str | Agent UUID |
name | str | Agent name |
user_prompt / goal | str | Instructions |
input_schema | dict | None | JSON Schema for runtime variables |
input_description | str | Plain-text description of the agent input |
output_schema | dict | None | JSON Schema for the agent's final result |
output_description | str | Plain-text description of the agent output |
available_tools | list | Tool IDs (list view) or tool objects (detail view) |
available_custom_mcp_servers | list[int] | Custom MCP server IDs attached |
status | AgentStatus | draft, initialization_required, active, archived |
all_tools_configured | bool | Whether all attached tools are configured |
generated_pipeline | dict | Auto-generated execution pipeline |
cron_schedule | str | Cron expression for scheduled runs (or "") |
webhook_url | str | None | Webhook URL for run notifications |
Run / RunDetail
| Field | Type | Description |
|---|---|---|
id | str | int | Execution ID (string slug on current backends, e.g. "won-gsfr-mxp") |
status | ExecutionStatus | pending, running, completed, failed, cancelled |
agent_result / output | dict | Final output |
error | str | Error message (if failed) |
messages | list[dict] | Full conversation history |
logs | list[ExecutionLog] | Step-by-step logs (RunDetail only) |
is_terminal | bool | Whether the run has finished |
original_prompt | str | The prompt used for this run |
ExecutionLog
| Field | Type | Description |
|---|---|---|
id | int | Log entry ID |
type | str | declared_functions, tool_called, tool_call_exception, task_cancelled |
message | str | Human-readable description |
data | dict | Structured payload (arguments, results, etc.) |
Tool
| Field | Type | Description |
|---|---|---|
id | int | Tool ID |
mcp_tool / name | str | Tool identifier (e.g. "github", "tavily") |
is_configured | bool | All configuration steps complete |
is_active | bool | Tool enabled |
next_configuration_step | dict | Next step to complete (or None) |
user_config | dict | Current configuration values |
AvailableTool
| Field | Type | Description |
|---|---|---|
name | str | Tool identifier |
type | str | oauth, custom, composio, custom_class |
title | str | Human-readable name |
description | str | Short description |
categories | list[str] | Categories (e.g. ["Development"]) |
Compilation
| Field | Type | Description |
|---|---|---|
id | int | Compilation ID |
status | str | pending, compiling, compiled, running, completed, failed |
script_code | str | Generated Python script |
result | dict | Execution result |
error | str | Error message (if failed) |