Several sessions and several VMs¶
Since 0.14.0.
A MicroVM with your agent serves a few sessions at a time. When you need more,
deploy_agent() gives you a fleet (AgentFleet): several MicroVMs of the
same image and a session spread done by the client, with no Rayito server in
between. With the defaults it is a single VM, the same as
AgentProject.deploy().
The options¶
Each option has its key in rayito-agent.toml, its argument in ServeOptions
and ScalingOptions, and its flag in rayito agent deploy.
| Option | What it does | Default | Limit |
|---|---|---|---|
max_concurrency (--max-sessions) |
sessions at a time in one VM | 1 | the connections of the VM size: 7 with 2 GB, 15 with 4 GB, 31 with 8 GB |
queue_max (--queue) |
requests that wait in the host queue when the VM is full | 0 (an immediate 429) | 256 |
queue_timeout_seconds (--queue-timeout) |
how long a request waits in the queue | 30 s | 900 s |
min_vms (--min-vms) |
VMs always alive | 1 | |
max_vms (--max-vms) |
VMs at most | 1 | 64 |
warm_vms (--warm) |
parked (suspended) slots that absorb a spike | 0 | max_vms - min_vms |
sessions (--sessions) |
shared (several sessions per VM) or dedicated (one VM per session) |
shared |
|
scale_in_after_seconds (--scale-in-after, since 0.17) |
idle time after which a VM above min_vms ends by itself |
900 s | 60 s |
idle (--idle) |
idle time after which a VM is suspended and resumed when called | no suspension | see Pause and resume |
The max_concurrency limit comes from the connections a MicroVM admits (8 per
base vCPU) minus one the service keeps for /ping: below 2 GB one vCPU is
counted, a conservative value. Raising the memory raises the cap.
The shortest way is the file (the keys you leave out are decided by detection):
# rayito-agent.toml
[serve]
max_concurrency = 2
queue_max = 4
[scaling]
min_vms = 1
max_vms = 3
warm_vms = 1
sessions = "shared"
[image]
memory_mib = 4096
from rayito import AgentDeployOptions, ScalingOptions, deploy_agent
fleet = deploy_agent(
"./mi-agente",
options=AgentDeployOptions(
bucket="amzn-s3-demo-bucket",
scaling=ScalingOptions(min_vms=1, max_vms=3, warm_vms=1, sessions="shared"),
),
)
try:
first = fleet.invoke("Hello")
again = fleet.invoke("What did I say?", session_id=first.session_id)
print(again.text)
finally:
fleet.stop()
import { deployAgent, ScalingOptions } from "rayito";
const fleet = await deployAgent("./mi-agente", {
options: {
bucket: "amzn-s3-demo-bucket",
scaling: new ScalingOptions({ minVms: 1, maxVms: 3, warmVms: 1, sessions: "shared" }),
},
});
try {
const first = await fleet.invoke("Hello");
const again = await fleet.invoke("What did I say?", { sessionId: first.sessionId });
console.log(again.text);
} finally {
await fleet.stop();
}
Shared and dedicated sessions¶
shared(the default): a new session goes to the VM with room; if all of them are full, to a warm slot; if there is none, to a new VM while you have not reachedmax_vms; and if you are already atmax_vms, it waits in the queue.dedicated: each new session gets its own MicroVM and a VM never serves two sessions. It is what you want if the agent keeps state on the VM disk or if you do not trust two users to share a process.
The session id and coming back to a conversation¶
With max_vms > 1, AgentResult.session_id is an identifier that names its
VM (rs1.<sandbox>.<session>): passing it back takes the call to the same VM
without asking anyone. With max_vms = 1 the session id does not change. From
another process, AgentFleet.connect(name, access_token=...) rebuilds the fleet
from the non-terminated MicroVMs of its image without resuming any, and a
session id from the first call still leads to its VM. For the reconnected fleet's
replies to return the handle too, pass the same scaling you deployed it with.
Since 0.17, a fleet reconnected with only its name and token can also create
VMs and start warm slots: it reads the recipe that
deploy_agent() saved. Without a recipe (a 0.16 fleet, or one deployed with
--no-save-recipe) and without project=, the fleet routes and queries but
cannot create a VM: the first session that needs one raises
InvalidArgumentException; pass the project then
(AgentProject.detect(folder) or AgentProject.load(...)).
A session id you choose yourself (a chat thread) is only remembered inside the
process that deployed the fleet. To find it from another process in dedicated
mode, turn on the optional metadata index
(Metadata index, with its
cost); without it, an unknown id starts a new VM and the response carries the
routable identifier you must keep.
The fleet recipe¶
Since 0.17.0.
After starting the fleet, deploy_agent() (and rayito agent deploy) saves its
recipe in Parameter Store, at /rayito/agents/<name>/recipe: the project as
it ended up after the flags (--env, --secret-env-from, --allow-out, the
gateways), the image version, the gateway presets, the scaling and whether keys
are renewed. It only holds secret names, never their values or the access
token.
With it, AgentFleet.connect(name, access_token=...) from any process (or
rayito agent invoke|status|stop <name>) creates VMs like the deployed ones,
opens the warm slots with their gateways and status() gives the real scaling.
An explicit scaling= or project= wins over the recipe, and
use_recipe=False (useRecipe: false) ignores it: the fleet is the 0.16 one.
- Status.
FleetStatus.scaling_source(scalingSource) tells where the scaling comes from:explicit,project,recipeordefault. Withdefault(no project and no recipe)rayito agent statusprintsmembers: N (escalado desconocido: sin proyecto ni receta)instead of amin 1, max 1that does not describe the fleet. - Deletion.
fleet.stop()andrayito agent stop <name>delete the recipe if this process saved or read it;stop(keep_recipe=True)(--keep-recipe) keeps it. - Warnings. A failure to save it never fails the deploy: it leaves a
warning with a code (
recipe_access_denied,recipe_not_saved,recipe_too_large) in the log and inwarningsof--json. When reading, no permission isrecipe_access_denied, and an unreadable recipe or a Parameter Store that does not answer (no SSM VPC endpoint, throttling) isrecipe_unreadable. A recipe that asked to renew the Bedrock key does not turn it on for the connecting process: withoutrenew_gateway_keys=Truethe warning isrecipe_renewal_ignoredand nothing is renewed. A fleet named.or..has no recipe (recipe_not_saved); in every case the fleet behaves as in 0.16, andrayito agent stop <name>still terminates its VMs (if it cannot delete the recipe either, it reports that error at the end). Afterstop, Parameter Store takes about 30 s before it lets another parameter use the same name: an immediatedeploywarnsrecipe_not_saved. - Size. A recipe is stored as JSON if it fits in 4,096 bytes (a standard
parameter) and compressed otherwise; if it still does not fit (very long
instructions), the warning is
recipe_too_largeand the previous one is deleted. --envand secrets.rayito agent deployrejects an--envwhose name looks like a secret (*_TOKEN,*_KEY,*_SECRET,*_PASSWORD..., or with aPASS,PASSWD,PAT,CREDENTIALorCREDENTIALSsegment, such asDB_PASSorGITHUB_PAT, or withAUTHorBEARERas the last segment or next toTOKEN,KEYorHEADER, such asX_AUTHorAUTH_HEADER;AUTH_MODEorAUTHOR_NAMEare accepted): use--secret-env NAME=<secret>or--secret-env-from <secret>, so the recipe only keeps the name. This breaks 0.16 command lines for*_SECRET. Note: the rule belongs to the--envflag; the project'senvvalues ([env]inrayito-agent.tomlorenv=in the library) are stored as is in the recipe, readable by anyone withssm:GetParameteronparameter/rayito/agents/*. Nothing secret inenv, not even a URL with credentials: it belongs insecret_env.- Who can write it. The recipe decides the image, egress,
envand each gateway's upstream and secrets for every process that connects by name: write access toparameter/rayito/agents/*amounts to deploy rights. That is whySandboxLauncherPolicyonly reads and deletes it, andssm:PutParameteris inImagePublisherPolicyandCallerPolicy.
Cost and activation
- Turns on:
deploy_agent()/rayito agent deployto MicroVMs, by default. It is the only Rayito feature on by default (ADR-034): the standard parameter is free and its KMS request falls in the free tier. - AWS resources and calls: one standard
SecureStringparameter per fleet (PutParameteron deploy,GetParameterwhen reconnecting without a project,DeleteParameteron stop), encrypted with the AWS managed keyaws/ssm. - Approximate cost: standard parameters have no charge; each write and each read is one KMS request (20,000 free per month, then $0.03 per 10,000). Checked on 2026-10-22: Systems Manager and KMS.
- IAM:
ssm:PutParameter(the deployer:CallerPolicyorImagePublisherPolicy),ssm:GetParameterandssm:DeleteParameteronparameter/rayito/agents/*(theAgentRecipestatement of IAM); no KMS permission. - How to turn it off:
AgentDeployOptions(save_recipe=False)(saveRecipe: false,--no-save-recipe). - Example:
rayito agent deploy ./mi-agente --no-save-recipe.
Warm slots and gateways¶
A parked slot is born without the project's gateways. Since 0.17, the fleet
opens them when it takes the slot, also when another process parked it (a fleet
reconnected with the recipe or with project=), through the same path as
SandboxPool.take(gateways=); if it cannot open them, it terminates the slot
and uses another VM. Before, a reconnected fleet could start the agent without
the model gateway, and deploys had to use --warm 0.
The queue and the 429¶
With max_concurrency = 1 and queue_max = 1, of three simultaneous requests
one runs, one waits for its turn and the third gets 429 with Retry-After.
The fleet client honours that Retry-After up to queue_timeout_seconds and,
after the deadline, raises CapacityException (code = "agent_capacity";
CapacityError in TypeScript).
Scaling in, suspending and restarting¶
- Scaling in: VMs above
min_vmscarry a deadline ofscale_in_after_secondsthat the fleet renews every time it uses them; an idle VM ends by itself (the VM'sraydalready knows how to terminate itself), with no control-plane call. While it serves any invocation, the deadline is never shorter than the request's, and it goes back toscale_in_after_secondsonly when the last one ends, also if it fails. Each VM's deadline changes go in order: releasing an invocation never shortens the deadline of another one that started meanwhile. A shortened VM that becomes a base VM gets its whole life back up torayd's cap, even when the fleet does not know it (it asks for the maximum andraydanswers with its cap); since 0.17.1. Forraydto accept that deadline, a fleet that can grow (max_vms > min_vms) creates all its VMs and slots with a logical deadline (on_timeout="kill"andmax_lifetime=serve.timeout_seconds + 60); a base VM still livesserve.timeout_seconds. A fixed-size fleet creates its VMs as before. Up to 0.16 scale-in did not happen: VMs were created without a logical deadline andraydrejected theset_timeout. A VM created that way (by 0.16) is not scaled in: the fleet warns once; end it withstop()or redeploy. - Suspending:
idlesuspends an unused VM and resumes it when called; a suspended VM bills no compute, only its snapshot storage. - Restarting: if the user's app dies, the host relaunches it with growing
waits (up to 3 times in 5 minutes). Meanwhile
/pingsaysHealthyBusyand invocations get503withRetry-After. Past the budget,/pingfails, the failure marker carriesapp_crashloopand the fleet replaces the VM; calls in flight on that VM fail withSandboxException(code="agent_vm_lost").
Cost and activation
- Activated by:
deploy_agent()with more than one possible VM (max_vms > 1) or with warm slots; with the defaults it is a single VM. - AWS resources and calls:
min_vmsMicroVMs running,warm_vmsslots parked in a pool, and the extra VMs the spread creates up tomax_vms;list-microvmsto find the fleet members. - Approximate cost: each running VM costs what a deployment costs (≈ $0.126 per hour with 2 GB, $0.25 with 4 GB); a parked slot only pays for its snapshot storage (≈ $0.011 per day); a suspended VM, the same. List prices, us-east-1, checked on 2026-10-06: Pricing.
- IAM: those of
Sandbox.create(),Sandbox.list(),Sandbox.connect()andSandbox.kill(); with a session index, those of the metadata index. - How to turn it off:
fleet.stop()orrayito agent stop <name>terminate every VM of the fleet (and delete its recipe); without callingdeploy_agent()nothing is created, and the defaults leave no slots or extra VMs. By name only VMs that belong to an agent are terminated: a tokenlessHealthreads the labels of theRUNNINGones, and if the image has one without them, none of the suspended ones is terminated (they cannot be probed without resuming them). - Example:
deploy_agent("./mi-agente", options=AgentDeployOptions(scaling=ScalingOptions(max_vms=3))).
Measured on real AWS
In the 0.14 acceptance (2026-10-16, us-east-1, Claude Haiku 4.5) the fifteen
sample agents were deployed without touching their code, in Python and in
TypeScript, with no options and with 4096 MiB, 2 sessions per VM, a queue of
2 and 1 to 2 VMs with one warm. Five simultaneous calls spread over two VMs;
through the URL of a single VM, the overflow got 429. Resuming a suspended
VM and making the first call took between 1.9 and 4.0 s. The whole cycle (65
images) cost about $5 and ended with the AWS inventory the same as at the
start. Details in AWS_API_NOTES.md (Q170 to Q172).
There is no fleet on AgentCore¶
With --target agentcore the agent runs on
Amazon Bedrock AgentCore Runtime, which creates one
microVM per session and scales on its own: min_vms, max_vms, warm_vms and
the rest of the options on this page that size a fleet do not exist there.
Passing them in the call is an error (option_not_supported) and, if they come
from the project file, a warning: Rayito never approximates them silently.
invoke(), stream() and the session_id are the same. The full table is in
Deploy to AgentCore.
See also¶
- Deploy your agent: the full guide and the project file.
- Deploy to AgentCore: the same agent on Amazon Bedrock AgentCore Runtime.
- Limits: the quotas and caps on this page.
- Pool: the parked slots a fleet uses.