Skip to main content

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​

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
)
Env variableDescriptionDefault
FLYMYAI_API_KEYAPI key (used when api_key is omitted)-
FLYMYAI_AGENTS_BASE_URLBase URL override for the agents APIhttps://backend.flymy.ai
Two distinct base URLs

The agents API (AgentClient) and the model-inference API (FlyMyAI, flymyai.run) live on different hosts and have separate env vars:

ClientDefault base URLEnv var override
AgentClient (this page)https://backend.flymy.aiFLYMYAI_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.

NamespaceWhat it does
client.agentsCreate, list, update, delete, and run agents
client.runsStart runs, poll for results, stream events, follow up
client.toolsBrowse the tool catalog, add and configure MCP tools
client.mcp_resource_setsCreate revisioned sets of exact MCP connections and slots
client.agent_groupsShare resource sets with flat agent groups
client.compilationsFreeze and run accepted instructions
client.versionsRead immutable versions materialized from accepted freezes
client.deploymentsPublish 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"
ParameterTypeRequiredDescription
namestrYesHuman-readable name
goalstrYesInstructions (supports Handlebars templating)
toolslist[int]NoTool IDs to attach
mcp_serverslist[int]NoCustom MCP server IDs to attach
input_schemadictNoJSON Schema for runtime variables (required if goal has placeholders)
input_descriptionstrNoPlain-text description of what the user provides; authoritative scope at freeze time
output_schemadictNoJSON Schema the agent must produce as its final result
output_descriptionstrNoPlain-text description of the result; pairs with output_schema to bound the canonical pipeline
statusstrNodraft (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"
tip

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:

TypeMeaning
declared_functionsAgent declared the tools it will use
tool_calledA tool was invoked
tool_call_exceptionA tool call failed
task_cancelledThe 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​

MethodWhat 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_schedule is a standard 5-field cron expression (minute hour day-of-month month day-of-week), e.g. */30 * * * * (every 30 minutes) or 0 0 * * 0 (Sundays at midnight).
  • timezone is an IANA name (e.g. UTC, America/New_York); defaults to UTC.
  • 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")
MethodWhat 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, viewVersions, 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_memberPeople it is shared with by name: invite, see who accepted, remove by member id
invitations, accept_invitation, decline_invitationInvitations 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",
)
MethodWhat 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
MethodWhat 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."}
ExceptionWhen
FlyMyAIAgentErrorAny non-2xx API response (has .status_code and .response_body)
TimeoutErrorruns.wait() or runs.stream_events() exceeded timeout
ValueErrorMissing api_key at initialization

Type reference​

Agent​

FieldTypeDescription
uuid / idstrAgent UUID
namestrAgent name
user_prompt / goalstrInstructions
input_schemadict | NoneJSON Schema for runtime variables
input_descriptionstrPlain-text description of the agent input
output_schemadict | NoneJSON Schema for the agent's final result
output_descriptionstrPlain-text description of the agent output
available_toolslistTool IDs (list view) or tool objects (detail view)
available_custom_mcp_serverslist[int]Custom MCP server IDs attached
statusAgentStatusdraft, initialization_required, active, archived
all_tools_configuredboolWhether all attached tools are configured
generated_pipelinedictAuto-generated execution pipeline
cron_schedulestrCron expression for scheduled runs (or "")
webhook_urlstr | NoneWebhook URL for run notifications

Run / RunDetail​

FieldTypeDescription
idstr | intExecution ID (string slug on current backends, e.g. "won-gsfr-mxp")
statusExecutionStatuspending, running, completed, failed, cancelled
agent_result / outputdictFinal output
errorstrError message (if failed)
messageslist[dict]Full conversation history
logslist[ExecutionLog]Step-by-step logs (RunDetail only)
is_terminalboolWhether the run has finished
original_promptstrThe prompt used for this run

ExecutionLog​

FieldTypeDescription
idintLog entry ID
typestrdeclared_functions, tool_called, tool_call_exception, task_cancelled
messagestrHuman-readable description
datadictStructured payload (arguments, results, etc.)

Tool​

FieldTypeDescription
idintTool ID
mcp_tool / namestrTool identifier (e.g. "github", "tavily")
is_configuredboolAll configuration steps complete
is_activeboolTool enabled
next_configuration_stepdictNext step to complete (or None)
user_configdictCurrent configuration values

AvailableTool​

FieldTypeDescription
namestrTool identifier
typestroauth, custom, composio, custom_class
titlestrHuman-readable name
descriptionstrShort description
categorieslist[str]Categories (e.g. ["Development"])

Compilation​

FieldTypeDescription
idintCompilation ID
statusstrpending, compiling, compiled, running, completed, failed
script_codestrGenerated Python script
resultdictExecution result
errorstrError message (if failed)