Every change Wellknown observed on this MCP server, newest first, with what it was before and what it became. Tool-surface changes carry the definition diff. Nothing here is edited after the fact.
Changed the definition of "delete_deployment", "deploy_app" and "deploy_compose"
Deletes a deployment and its underlying appVM.VM, with the data kept on that machine (its named volumes): the only way kept data is deleted, so ask the user first. Pass the numeric id from list_deployments. IMPORTANT: ⟨43 unchanged words⟩
⟨215 unchanged words⟩ into /var/www/html/wp-content/uploads.","type":"boolean"},"data_paths":{"description":"Directories the app WRITES its data to inside its container (e.g. '/var/lib/myapp', '/app/data', a SQLite file's folder, an uploads folder): the data is kept on this machine, in named volumes, together with every VOLUME the image declares (kept without being named here). It survives a step-up, a restart, a reboot and every redeploy of the name, which runs on this same machine. Name the folder, never a file and never the code folder (redu refuses the image's working directory, which would keep the old code). Absolute paths, at most 16; a path once kept stays kept. While the deployment keeps data, a deploy that would need a new machine is refused: another size, port, keypair, hostname, route or worker mode, a single_vm database, or a stopped machine (start it with instance_action). To start fresh, delete_deployment (it deletes the data). A VOLUME the image declares is kept even when it holds code (for example a WordPress image's /var/www/html), so a redeploy does not replace code baked under it. Read the app's docs, Dockerfile or config for where it stores data.","items":{"type":"string"},"type":"array"},"database":{"description":"DB wiring (auto-injects ⟨1361 unchanged words⟩
⟨231 unchanged words⟩ into /var/www/html/wp-content/uploads.","type":"boolean"},"data_paths":{"description":"Directories a service WRITES its data to that the compose file's own volumes do not cover: 'service:/path', or '/path' for the exposed `service`. The data is kept on this machine, in named volumes (an override file adds them; your compose file is never rewritten), and survives every redeploy of the name, which runs on this same machine. The compose file's own volumes, and the VOLUMEs its images declare, are kept as they are and never removed: you need this only for a path no volume covers. At most 16; a path once kept stays kept. While the deployment keeps data, a deploy that would need a new machine is refused: another size, port, keypair, hostname or route, several exposed surfaces, a single_vm database, compose_engine podman, or a stopped machine (start it with instance_action). To start fresh, delete_deployment (it deletes the data). A VOLUME an image declares is kept even when it holds code (for example a WordPress image's /var/www/html), so a redeploy does not replace code baked under it.","items":{"type":"string"},"type":"array"},"database":{"description":"DB mode: 'managed' ⟨1172 unchanged words⟩
Added "delete_dns_entry" and "release_domain"; changed the definition of "deploy_app", "deploy_compose", "get_domain_verification" and 4 more (86 tools before, 88 now)
⟨140 unchanged words⟩ unsafe local uploads. Build+provision takes minutes; poll list_deployments/get_deployment. ROUTE: when plan_deploy returned recommended_route:'compose', do NOT use this tool for that folder: deploy its recommended_compose_file with deploy_compose (compose_file, recommended_service as service, recommended_port as port), for the reason in route_reason. The exceptions are route_next's: this tool builds the folder's Dockerfile instead only if the user asked for a build from source, the user changed the code the Dockerfile builds (check git status and unpushed commits in the folder), or the image cannot be pulled.
⟨181 unchanged words⟩ media space into /var/www/html/wp-content/uploads. Poll get_deployment until ready. ROUTE: when plan_deploy returned recommended_route:'compose', this is the tool for that folder, even when it also has a Dockerfile: pass its recommended_compose_file as compose_file, recommended_service as service and recommended_port as port (the HOST port the app service publishes, not the port the app listens on inside its container). route_reason says why the platform deploys it that way, and route_next when to build the Dockerfile with deploy_app instead.
Changed the definition of "attach_volume", "check_agent_prerequisites", "check_deploy_prerequisites" and 27 more
⟨35 unchanged words⟩ attach_volume puts that volume on a replacement instance—- then SSH in (get_ssh_command) and mount it ⟨12 unchanged words⟩ volume must be 'available' (not already in-use—- detach_volume it first) and in the same ⟨33 unchanged words⟩
⟨9 unchanged words⟩ to attach it to / detach it from—- from list_instances (id).","minLength":1 ⟨2 unchanged words⟩ "volume_id":{"description":"Volume to attach/detach—
Changed the definition of "upgrade_to_cluster" and "upgrade_to_ha"
⟨476 unchanged words⟩ 409 cluster_needs_stateless unless confirm_stateless:true. To have reduFIXhelp with a stateful VMfor youinstead of refusing, pass auto_restructure:true—: for a single_vm Postgresitthefully-automaticallyfullyprovisionsautomaticamovemanagedisDB
Added "delete_api_key" and "list_api_keys"; changed the definition of "deploy_app" and "deploy_compose" (84 tools before, 86 now)
⟨37 unchanged words⟩ ,"woocommerce"],"type":"string"},"approved":{"description":"ONLY when send_signals said approval_required (this account approves before anything paid starts): true after the USER said yes to the plan and its cost in this conversation. Never set it yourself, and leave it out in every other case.","type":"boolean"},"cluster_media_mode":{"description":"Record the WordPress media ⟨1496 unchanged words⟩
⟨37 unchanged words⟩ ,"woocommerce"],"type":"string"},"approved":{"description":"ONLY when send_signals said approval_required (this account approves before anything paid starts): true after the USER said yes to the plan and its cost in this conversation. Never set it yourself, and leave it out in every other case.","type":"boolean"},"
Added "create_qdrant", "delete_qdrant" and "list_qdrant"; changed the definition of "delete_deployment", "deploy_app", "deploy_compose" and 4 more (81 tools before, 84 now)
⟨8 unchanged words⟩ Pass the numeric id from list_deployments. IMPORTANT:if the deployment used database:'managed', theits managedPostgres VMdatastoresisare NOT deleted (data safety):—its database, Redis and Qdrant keep running and billing, and this tool returnsitseach id so you can delete_databaseit/ delete_relational_database / delete_redis / delete_qdrant them when you're done with the data. Cannot be undone.
Changed the definition of "attach_volume", "check_deploy_prerequisites", "create_backup" and 17 more
⟨43 unchanged words⟩ ,"request":{},"result":{},"source_mismatch":{"type":"boolean"},"validation":{}},"type":"object"}
⟨5 unchanged words⟩ ,"properties":{"mode":{"description":"guided = confirm eachOMITchoicebywithdefault: theuser;
Changed the definition of "attach_volume", "create_backup", "create_clickhouse" and 15 more
⟨21 unchanged words⟩ ,"invalid_keypair_name":{"type":"boolean"},"machine":{},"mode":{"type":"string"},"needs_keypair" ⟨19 unchanged words⟩
⟨21 unchanged words⟩ ,"invalid_keypair_name":{"type":"boolean"},"machine":{},"mode":{"type":"string"},"needs_keypair" ⟨19 unchanged words⟩
Certificate changed, valid to 2027-01-03
Certificate recorded, valid to 2026-11-04
Authorization not required
First tool surface recorded: 81 tools (server version 2.0.0)
Showing the latest 12 events. The API returns up to 500 and filters by kind: ?kind=tool_surface_changed
⟨1090 unchanged words⟩ , so a wrong value fails the deploy. When plan_deploydetectsrecommendsit.the compose route, its recommended_port is this port.","maximum":65535,"minimum":1,"type ⟨299 unchanged words⟩
ReturnsStarts (or resumes) a claim on a custom domain and returns the DNS TXT recordtothataddprovesforitcustomisdomaintheownershipuser's:verification.theyAddaddtheitrecordattotheyourdomain's DNS provider, then you call verify_domain. Until it verifies, list_domains shows the domain as pending. A pending claim's TXT value can change (values are specific to each account), so add exactly the value get_domain_verification returns now, never one saved earlier or seen on another account; if verify_domain keeps failing, call get_domain_verification again and update the record when the value differs. A name under redu.cloud needs no verification.
{"$schema":"http://json-schema.org/draft-07/schema#","additionalProperties":false,"properties":{"domain":{"type":"string"},"mode":{"type":"string"},"next":{"type":"string"},"record_name":{"type":"string"},"record_type":{"type":"string"},"record_value":{"type":"string"}},"type":"object"}
Lists the public names (DNSproxyentries)hostonentries.this account: each entry's id, the names it serves (domain_names) and the address it forwards to. delete_dns_entry removes one by its id.
Listverifiedcustom domains
Lists the custom domainsyouonhavethis account with their status: verifiedownership(itof.can be bound with dname on deploy_app/deploy_compose, dns_entries on create_instance, or set_cluster_domain) or pending (its TXT check has not passed: get_domain_verification gives the record, verify_domain checks it). The public names that use a domain are in list_dns_entries. release_domain removes a domain from the account.
⟨690 unchanged words⟩ is redu.md, so do not skip creating it. ROUTE: from a source_token (pass port too: it tells the platform which compose service is the app) the plan can also carry recommended_route:'compose', the platform's route for that folder, with every recommended_* and route_* field it sent (recommended_compose_file, recommended_service, recommended_port, recommended_image, route_reason, route_next): then deploy that compose file with deploy_compose (compose_file), with recommended_service as service and recommended_port as port (the HOST port the app service publishes, not the port the app listens on inside its container), not deploy_app, unless route_next's conditions for building the Dockerfile with deploy_app hold.
{"$schema":"http://json-schema.org/draft-07/schema#","additionalProperties":falsetrue,"properties":{"deployable":{"type":" ⟨34 unchanged words⟩ ,"reason":{"type":"string"},"recommended_compose_file":{"type":["string","null"]},"recommended_image":{"type":["string","null"]},"recommended_port":{"anyOf":[{"type":"integer"},{"type":"null"}]},"recommended_route":{"type":"string"},"recommended_service":{"type":["string","null"]},"report_filename":{"type":"string"},"report_markdown":{"type":"string"},"route_next":{"type":["string","null"]},"route_reason":{"type":["string","null"]},"source_files_error":{"type":["string", ⟨12 unchanged words⟩
Checks the DNS TXT ownership recordforof a custom domain and,markswhen itverifiedmatches,whenmarks therecorddomainisverifiedpresent.for this account. Call get_domain_verification first for the record,addhave theTXTuserrecordadd it atyourtheir DNS provider, then call this untilstatusitisreportsverified.verified (a new record can take minutes, sometimes hours, to show). A verified domain can be bound: dname on deploy_app/deploy_compose, dns_entries on create_instance, or set_cluster_domain. A pending claim's TXT value can change (values are specific to each account), so add exactly the value get_domain_verification returns now, never one saved earlier or seen on another account; if verify_domain keeps failing, call get_domain_verification again and update the record when the value differs.
⟨16 unchanged words⟩ ,"status":{"type":"string"},"verified":{"type":"boolean"},"verified_at":{"type":["string","null" ⟨2 unchanged words⟩
Remove a public name (needs approval) — Removes one public name (a DNS entry), by its id from list_dns_entries or by the name itself. The name stops serving at once, and anything reached through it (a site, an API, an OAuth callback) stops working with it. When the name belongs to one of your instances or deployments, that instance's own list of its names is updated too, the way the console's DNS view does it; the instance, its app and its data are untouched. The first call removes nothing: it shows the entry for the user to approve; call again with confirm:true only after their yes. Use it to clear the names a release_domain refus…
Release a custom domain (needs approval) — Removes a custom domain from this account: a verified domain is no longer verified for it (using it again needs a new TXT check), and a pending claim is cancelled. The first call releases nothing: it shows the domain for the user to approve; call again with confirm:true only after their yes. The second call reports the platform's answer: released, or refused. A refusal because public names still use the domain (DOMAIN_IN_USE) lists those names, each with its id: remove each with delete_dns_entry (it asks for the user's yes first), then call this again. Releasing a domain deletes no deployment…
⟨21 unchanged words⟩ keypair, and billing. Safe to call anytime—- read-only. Call this first before create_controller.
The deploy ENTRY GATE—- run this first when a user wants to ⟨27 unchanged words⟩ 2) a private network (picks your default—- every account has one), (3) an ⟨10 unchanged words⟩ local ssh-keygen → import_keypair → write SSH_KEY_LOCATION-to-.env steps—- run them, in yolo mode without asking) ⟨8 unchanged words⟩
⟨19 unchanged words⟩ :"ID of the instance to back up—- its bootable (else first) attached volume is ⟨41 unchanged words⟩
⟨11 unchanged words⟩ on a dedicated VM on your private network—- its OWN resource, NOT a relational database. ⟨18 unchanged words⟩ event analytics, time-series). It is PRIVATE—- reachable only from another instance on the same ⟨19 unchanged words⟩ Get the ids from plan_managed_datastore/list_flavors (use m1.small+—- ClickHouse needs >=2GB RAM), list_private_networks, list_keypairs. ⟨94 unchanged words⟩
⟨62 unchanged words⟩ description":"Instance size for the ClickHouse VM—- from list_flavors. ClickHouse is RAM-hungry; pick m1.small ( ⟨360 unchanged words⟩ "keypair_name":{"description":"SSH keypair name—- from list_keypairs.","minLength":1,"type": ⟨20 unchanged words⟩ "network_id":{"description":"Private network id—- from list_private_networks. The DB is reachable only from ⟨144 unchanged words⟩
⟨21 unchanged words⟩ you don't have one. The VM bootstraps itself—- no SSH or VPN needed. After ~7 minutes ⟨12 unchanged words⟩
⟨27 unchanged words⟩ and wires the DB automatically. It is PRIVATE—- reachable only from another instance on the same ⟨121 unchanged words⟩
⟨116 unchanged words⟩ description":"Instance size for the DB VM—- from list_flavors.","minLength":1,"type": ⟨268 unchanged words⟩ "keypair_name":{"description":"SSH keypair name—- from list_keypairs (for operating the DB VM) ⟨23 unchanged words⟩ "network_id":{"description":"Private network id—- from list_private_networks. The DB is reachable only from ⟨15 unchanged words⟩ {"description":"Grant the db user SUPERUSER—- safe here (a dedicated single-tenant DB VM) ⟨38 unchanged words⟩
⟨12 unchanged words⟩ network. Requires a recent plan_managed_datastore. It is PRIVATE—- reachable only from another instance on the same ⟨22 unchanged words⟩ from plan_managed_datastore/list_flavors, list_private_networks (or check_deploy_prerequisites), list_keypairs—- use the SAME network_id as the app that ⟨33 unchanged words⟩
⟨7 unchanged words⟩ description":"Instance size for the Redis VM—- from list_flavors. m1.small (2GB) is plenty for ⟨11 unchanged words⟩ "keypair_name":{"description":"SSH keypair name—- from list_keypairs (for operating the Redis VM) ⟨24 unchanged words⟩ "network_id":{"description":"Private network id—- from list_private_networks (or check_deploy_prerequisites). Redis is ⟨68 unchanged words⟩
⟨7 unchanged words⟩ on a dedicated VM on your private network—- the relational-database resource (use this instead of ⟨31 unchanged words⟩ and wires the DB automatically. It is PRIVATE—- reachable only from another instance on the same ⟨46 unchanged words⟩ connect cleanly. (ClickHouse is a separate resource—- use create_clickhouse / list_clickhouse_databases.)
⟨85 unchanged words⟩ uses. (For analytics/column-store needs use create_clickhouse instead—- ClickHouse is not a relational DB.)"," ⟨6 unchanged words⟩ description":"Instance size for the DB VM—- from list_flavors.","minLength":1,"type": ⟨6 unchanged words⟩ "keypair_name":{"description":"SSH keypair name—- from list_keypairs (for operating the DB VM) ⟨23 unchanged words⟩ "network_id":{"description":"Private network id—- from list_private_networks. The DB is reachable only from ⟨41 unchanged words⟩
Deletes an autoscaling cluster—- tears down the WHOLE cluster: every autoscaled ⟨24 unchanged words⟩ part of the stack and is NOT deleted—- it was the always-on baseline member but it ⟨26 unchanged words⟩ (the autoscaling group re-creates them under load)—- this deletes the group itself. The managed database ⟨25 unchanged words⟩
⟨14 unchanged words⟩ undone. The volume must be 'available' (detached)—- detach_volume it first, and note you cannot ⟨35 unchanged words⟩
⟨15 unchanged words⟩ The container is built ON the VM. PREREQS—- run check_deploy_prerequisites first for network_id + keypair_name, ⟨117 unchanged words⟩
before
{"$schema":"http://json-schema.org/draft-07/schema#","additionalProperties":false,"properties":{"app_profile":{"description":"Detected app profile from source inspection. For WordPress/WooCommerce pass wordpress/woocommerce so cluster deploys enforce managed DB + shared media instead of treating local uploads as stateless.","enum":["generic","wordpress","woocommerce"],"type":"string"},"approved":{"description":"ONLY when send_signals said approval_required (this account approves before anything paid starts): true after the USER said yes to the plan and its cost in this conversation. Never set it yourself, and leave it out in every other case.","type":"boolean"},"cluster_media_mode":{"description":"Record the WordPress media strategy for future clustering. For real WordPress clusters use media_space by default: Redu mounts a shared uploads filesystem into every member. local_uploads is single-VM only and upgrade_to_cluster will refuse it for WordPress.","enum":["local_uploads","central_media_origin","media_space"],"type":"string"},"cluster_target":{"description":"Set TRUE when the user asked to deploy this app as an autoscaling cluster. For WordPress/WooCommerce this makes the backend require managed MySQL/MariaDB plus media_space/central_media_origin before deployment can proceed.","type":"boolean"},"context":{"description":"Build-context dir within the source (default: repo root, or `subdir`). Set when the Dockerfile lives in a subfolder but builds from the repo root.","type":"string"},"create_media_space":{"description":"For WordPress/WooCommerce cluster_target:true: set TRUE when no suitable media space exists. Redu creates an NFS media VM + persistent volume and mounts it into /var/www/html/wp-content/uploads.","type":"boolean"},"database":{"description":"DB wiring (auto-injects the connection env + DATABASE_URL, zero setup): 'managed' = a SEPARATE managed-DB VM on the same private network (data PERSISTS across app redeploys; reused on a same-name redeploy); 'single_vm' = Postgres ON the app VM (data dies when the VM is replaced; Postgres only; with a signal, only when the user asked for one VM). 'none' = no DB. You never call create_database/create_relational_database for an app.","enum":["none","single_vm","managed"],"type":"string"},"db_engine":{"description":"Managed-DB engine (default 'postgres'). 'mysql'/'mariadb' provision a managed MySQL/MariaDB VM and wire MYSQL_HOST/MYSQL_PORT/MYSQL_USER/MYSQL_PASSWORD/MYSQL_DATABASE + a mysql:// DATABASE_URL — use for WordPress/Matomo/NextCloud/most PHP-LAMP apps. Requires database:'managed' (single_vm is Postgres only).","enum":["postgres","mysql","mariadb"],"type":"string"},"db_extensions":{"description":"managed/single_vm Postgres only: extensions to pre-install (pgvector, postgis, pgaudit, pg_trgm, …). Pass when the app needs one — e.g. langfuse/lantern need pgvector — so you don't have to CREATE EXTENSION by hand.","items":{"enum":["vector","pgvector","postgis","pgaudit","pg_stat_statements","hstore","pg_trgm","uuid-ossp","citext","pgcrypto","ltree","btree_gin","btree_gist"],"type":"string"},"type":"array"},"db_flavor_id":{"description":"managed only: VM size for the dedicated Postgres (from list_flavors). Defaults to the app flavor; m1.small is plenty for most. plan_deploy sizes this for you.","type":"string"},"db_id":{"description":"Informational link to a managed Postgres (from create_database/list_databases).","exclusiveMinimum":0,"type":"integer"},"db_name":{"description":"DB name for single_vm/managed (default 'app').","type":"string"},"db_superuser":{"description":"managed/single_vm Postgres only: grant the app DB user SUPERUSER (dedicated single-tenant DB VM, so safe). Use when the app's migrations create extensions/roles themselves.","type":"boolean"},"db_user":{"description":"DB user for single_vm/managed (default 'appuser').","type":"string"},"db_version":{"description":"DB version for single_vm/managed. Postgres: '16'|'15'|'14' (default 16). MySQL: '8.0'. MariaDB: '11.4'|'10.11'. Defaults per engine if omitted.","type":"string"},"dname":{"description":"Custom *.redu.cloud subdomain. For a STABLE, KNOWN-AHEAD URL (needed when the app must be told its OWN url — OAuth callbacks, cookie domain, a frontend that calls its API), generate the FULL auto-gen form yourself: `<label>-<8 lowercase letters/digits>.redu.cloud` (e.g. `myapp-7k2m9x4p.redu.cloud`) — that exact form is used VERBATIM — and wire the app's own URL env to that SAME value. A BARE `<label>.redu.cloud` is NOT used as-is: redu appends a random 8-char suffix for uniqueness, so anything wired to the bare name will NOT match the real URL. Omit to auto-generate (then read the real URL from get_deployment).","type":"string"},"dockerfile":{"description":"Path within the source to the Dockerfile (e.g. 'scripts/Dockerfile') when it's NOT at the root. Pair with `context` when the Dockerfile is in a subfolder but COPYs from the repo root.","type":"string"},"dockerfile_content":{"description":"A Dockerfile to write into the build dir before building — pass the Dockerfile that plan_deploy generated when the repo has none, or to override a broken one. (In upload mode you can instead just include the Dockerfile in the tarball.)","type":"string"},"env":{"additionalProperties":{"type":"string"},"description":"Env vars injected into the container at deploy time. Never baked into the image. With a managed store the platform also injects DATABASE_URL and PGHOST/PGPORT/PGUSER/PGPASSWORD/PGDATABASE (MYSQL_* for MySQL/MariaDB), REDIS_URL/REDIS_HOST/REDIS_PORT/REDIS_PASSWORD, QDRANT_URL/QDRANT_API_KEY. If the app reads OTHER names, map them here with references the VM fills in: e.g. {DB_TYPE: 'postgres', DB_HOST: '${PGHOST}', DB_PORT: '${PGPORT}', DB_USER: '${PGUSER}', DB_PASS: '${PGPASSWORD}', DB_NAME: '${PGDATABASE}'}. Check which names the app reads (its docs, its config code): otherwise its managed store sits unused.","type":"object"},"flavor_id":{"description":"Instance size — from list_flavors. Default m1.medium (enough RAM to build on the VM). For a RAM-heavy app whose state is in a managed DB (Next.js/cal.com, Rails, JVM), prefer m1.mem16/m1.mem32: full RAM on a lean 40 GB disk, so it's cheaper and snapshots/clusters far faster than the 160 GB m1.xlarge/xlarge-highmem. If you will CLUSTER this app (upgrade_to_cluster), prefer m1.mem16/m1.mem32 regardless of RAM needs — a lean disk makes clustering and every scale-out several times faster (plan_deploy sizes cluster_target onto a mem flavor for you).","minLength":1,"type":"string"},"git_ref":{"description":"git mode only: branch/tag/commit to deploy (default: the repo's default branch).","type":"string"},"git_token":{"description":"git mode only: token to clone a PRIVATE repo. Omit for public repos.","type":"string"},"idempotency_key":{"minLength":8,"type":"string"},"keypair_name":{"description":"REQUIRED. An EXISTING SSH keypair name — call list_keypairs and reuse one, or import_keypair first.","minLength":1,"type":"string"},"machine_id":{"description":"EXPERIMENT: the prepared machine to run this deploy on, from prepare_machine. Omit: the newest one this session has not used yet.","exclusiveMinimum":0,"type":"integer"},"media_mount_path":{"description":"Host mount path on the app VM/member. Redu mounts this into /var/www/html/wp-content/uploads.","type":"string"},"media_origin_url":{"description":"Public base URL where WordPress wp-content/uploads is served when using central_media_origin. Do not put this on the DB VM.","format":"uri","type":"string"},"media_space_flavor_id":{"description":"Flavor id for the media VM when create_media_space:true. Defaults to m1.small/backend default.","minLength":1,"type":"string"},"media_space_id":{"description":"Existing Redu media space id to mount at WordPress wp-content/uploads. Get it from list_media_spaces.","exclusiveMinimum":0,"type":"integer"},"media_space_name":{"description":"Optional media space name when create_media_space:true. Defaults to <deployment-name>-media.","maxLength":63,"minLength":1,"type":"string"},"media_space_size_gb":{"description":"Media space data volume size in GB when create_media_space:true (default 20).","maximum":2048,"minimum":1,"type":"integer"},"name":{"description":"Name for the deployment / VM (lowercase letters, numbers, hyphens).","maxLength":63,"minLength":1,"type":"string"},"network_id":{"description":"Existing private network id — from check_deploy_prerequisites (auto-selects your default) or list_private_networks. Optional: if omitted, redu auto-selects your default network.","minLength":1,"type":"string"},"port":{"default":3000,"description":"REQUIRED in practice: the port the app actually listens on inside the container — pass the port plan_deploy detected / the Dockerfile EXPOSE / the framework default. redu probes THIS port for health, so a wrong value fails the deploy. Defaults to 3000 only if omitted (e.g. a static nginx app listens on 80 — pass 80). IGNORED when worker:true.","maximum":65535,"minimum":1,"type":"integer"},"qdrant":{"description":"Vector DB wiring: 'managed' = a managed Qdrant on the same private network, wired as QDRANT_URL/QDRANT_HOST/QDRANT_PORT/QDRANT_GRPC_PORT/QDRANT_API_KEY (data persists across redeploys). The app must send QDRANT_API_KEY as its api-key; every managed Qdrant requires one. You never call create_qdrant for an app.","enum":["none","managed"],"type":"string"},"qdrant_flavor_id":{"description":"managed Qdrant only: the Qdrant VM size (from list_flavors).","type":"string"},"redis":{"description":"Redis wiring (auto-injects REDIS_URL/REDIS_HOST/REDIS_PORT/REDIS_PASSWORD, zero setup): 'managed' = a SEPARATE managed Redis VM on the same private network (data persists across app redeploys; reused on a same-name redeploy). You never call create_redis for an app.","enum":["none","managed"],"type":"string"},"redis_flavor_id":{"description":"managed Redis only: the dedicated Redis VM size (from list_flavors). Defaults to the app flavor; m1.small is plenty for a cache.","type":"string"},"redis_password":{"description":"managed Redis only: a specific password to set (otherwise auto-generated).","type":"string"},"redis_version":{"description":"managed Redis only: version (default '7').","enum":["7","6"],"type":"string"},"repo":{"description":"Filled in for you with the repository URL the USER gave (url_given_by_user in send_signals); the VM clones it. Otherwise omit it and pass source_token: the folder you work in is the source, and the platform refuses a repo it was not given. For a PRIVATE repo also pass git_token.","format":"uri","type":"string"},"runtime":{"description":"Informational: node/python/go/… (e.g. from plan_deploy).","type":"string"},"security_group_names":{"default":["default"],"items":{"type":"string"},"type":"array"},"source_token":{"description":"UPLOAD MODE: token from prepare_upload's curl step — deploys an uploaded tarball of your LOCAL working dir (no git, no PAT). Use this to deploy uncommitted code, a fixed clone of a repo you don't own, or private code. Omit `repo` when set.","minLength":16,"type":"string"},"subdir":{"description":"Build context within the source (e.g. 'demo-go') when the Containerfile/Dockerfile isn't at the root.","type":"string"},"worker":{"description":"HEADLESS WORKER / daemon mode. Deploys a long-running container with NO HTTP server, NO public URL, and NO health probe — ready = the container stays running. The VM still gets a floating IP + SSH stream (so you can SSH in), but nothing is exposed on the web. Use for background workers, a chaos-monkey, an on-infra agent runner, cron/queue consumers. `port`, `dname`, and the injected APP_URL/PUBLIC_URL are ignored. Run plan_deploy with worker:true too.","type":"boolean"}},"required":["name","keypair_name"],"type":"object"}after
{"$schema":"http://json-schema.org/draft-07/schema#","additionalProperties":false,"properties":{"app_profile":{"description":"Detected app profile from source inspection. For WordPress/WooCommerce pass wordpress/woocommerce so cluster deploys enforce managed DB + shared media instead of treating local uploads as stateless.","enum":["generic","wordpress","woocommerce"],"type":"string"},"approved":{"description":"ONLY when send_signals said approval_required (this account approves before anything paid starts): true after the USER said yes to the plan and its cost in this conversation. Never set it yourself, and leave it out in every other case.","type":"boolean"},"cluster_media_mode":{"description":"Record the WordPress media strategy for future clustering. For real WordPress clusters use media_space by default: Redu mounts a shared uploads filesystem into every member. local_uploads is single-VM only and upgrade_to_cluster will refuse it for WordPress.","enum":["local_uploads","central_media_origin","media_space"],"type":"string"},"cluster_target":{"description":"Set TRUE when the user asked to deploy this app as an autoscaling cluster. For WordPress/WooCommerce this makes the backend require managed MySQL/MariaDB plus media_space/central_media_origin before deployment can proceed.","type":"boolean"},"context":{"description":"Build-context dir within the source (default: repo root, or `subdir`). Set when the Dockerfile lives in a subfolder but builds from the repo root.","type":"string"},"create_media_space":{"description":"For WordPress/WooCommerce cluster_target:true: set TRUE when no suitable media space exists. Redu creates an NFS media VM + persistent volume and mounts it into /var/www/html/wp-content/uploads.","type":"boolean"},"database":{"description":"DB wiring (auto-injects the connection env + DATABASE_URL, zero setup): 'managed' = a SEPARATE managed-DB VM on the same private network (data PERSISTS across app redeploys; reused on a same-name redeploy); 'single_vm' = Postgres ON the app VM (data dies when the VM is replaced; Postgres only; with a signal, only when the user asked for one VM). 'none' = no DB. You never call create_database/create_relational_database for an app.","enum":["none","single_vm","managed"],"type":"string"},"db_engine":{"description":"Managed-DB engine (default 'postgres'). 'mysql'/'mariadb' provision a managed MySQL/MariaDB VM and wire MYSQL_HOST/MYSQL_PORT/MYSQL_USER/MYSQL_PASSWORD/MYSQL_DATABASE + a mysql:// DATABASE_URL - use for WordPress/Matomo/NextCloud/most PHP-LAMP apps. Requires database:'managed' (single_vm is Postgres only).","enum":["postgres","mysql","mariadb"],"type":"string"},"db_extensions":{"description":"managed/single_vm Postgres only: extensions to pre-install (pgvector, postgis, pgaudit, pg_trgm, …). Pass when the app needs one - e.g. langfuse/lantern need pgvector - so you don't have to CREATE EXTENSION by hand.","items":{"enum":["vector","pgvector","postgis","pgaudit","pg_stat_statements","hstore","pg_trgm","uuid-ossp","citext","pgcrypto","ltree","btree_gin","btree_gist"],"type":"string"},"type":"array"},"db_flavor_id":{"description":"managed only: VM size for the dedicated Postgres (from list_flavors). Defaults to the app flavor; m1.small is plenty for most. plan_deploy sizes this for you.","type":"string"},"db_id":{"description":"Informational link to a managed Postgres (from create_database/list_databases).","exclusiveMinimum":0,"type":"integer"},"db_name":{"description":"DB name for single_vm/managed (default 'app').","type":"string"},"db_superuser":{"description":"managed/single_vm Postgres only: grant the app DB user SUPERUSER (dedicated single-tenant DB VM, so safe). Use when the app's migrations create extensions/roles themselves.","type":"boolean"},"db_user":{"description":"DB user for single_vm/managed (default 'appuser').","type":"string"},"db_version":{"description":"DB version for single_vm/managed. Postgres: '16'|'15'|'14' (default 16). MySQL: '8.0'. MariaDB: '11.4'|'10.11'. Defaults per engine if omitted.","type":"string"},"dname":{"description":"Custom *.redu.cloud subdomain. For a STABLE, KNOWN-AHEAD URL (needed when the app must be told its OWN url - OAuth callbacks, cookie domain, a frontend that calls its API), generate the FULL auto-gen form yourself: `<label>-<8 lowercase letters/digits>.redu.cloud` (e.g. `myapp-7k2m9x4p.redu.cloud`) - that exact form is used VERBATIM - and wire the app's own URL env to that SAME value. A BARE `<label>.redu.cloud` is NOT used as-is: redu appends a random 8-char suffix for uniqueness, so anything wired to the bare name will NOT match the real URL. Omit to auto-generate (then read the real URL from get_deployment).","type":"string"},"dockerfile":{"description":"Path within the source to the Dockerfile (e.g. 'scripts/Dockerfile') when it's NOT at the root. Pair with `context` when the Dockerfile is in a subfolder but COPYs from the repo root.","type":"string"},"dockerfile_content":{"description":"A Dockerfile to write into the build dir before building - pass the Dockerfile that plan_deploy generated when the repo has none, or to override a broken one. (In upload mode you can instead just include the Dockerfile in the tarball.)","type":"string"},"env":{"additionalProperties":{"type":"string"},"description":"Env vars injected into the container at deploy time. Never baked into the image. With a managed store the platform also injects DATABASE_URL and PGHOST/PGPORT/PGUSER/PGPASSWORD/PGDATABASE (MYSQL_* for MySQL/MariaDB), REDIS_URL/REDIS_HOST/REDIS_PORT/REDIS_PASSWORD, QDRANT_URL/QDRANT_API_KEY. If the app reads OTHER names, map them here with references the VM fills in: e.g. {DB_TYPE: 'postgres', DB_HOST: '${PGHOST}', DB_PORT: '${PGPORT}', DB_USER: '${PGUSER}', DB_PASS: '${PGPASSWORD}', DB_NAME: '${PGDATABASE}'}. Check which names the app reads (its docs, its config code): otherwise its managed store sits unused.","type":"object"},"flavor_id":{"description":"Instance size - from list_flavors. Default m1.medium (enough RAM to build on the VM). For a RAM-heavy app whose state is in a managed DB (Next.js/cal.com, Rails, JVM), prefer m1.mem16/m1.mem32: full RAM on a lean 40 GB disk, so it's cheaper and snapshots/clusters far faster than the 160 GB m1.xlarge/xlarge-highmem. If you will CLUSTER this app (upgrade_to_cluster), prefer m1.mem16/m1.mem32 regardless of RAM needs - a lean disk makes clustering and every scale-out several times faster (plan_deploy sizes cluster_target onto a mem flavor for you).","minLength":1,"type":"string"},"git_ref":{"description":"git mode only: branch/tag/commit to deploy (default: the repo's default branch).","type":"string"},"git_token":{"description":"git mode only: token to clone a PRIVATE repo. Omit for public repos.","type":"string"},"idempotency_key":{"minLength":8,"type":"string"},"keypair_name":{"description":"REQUIRED. An EXISTING SSH keypair name - call list_keypairs and reuse one, or import_keypair first.","minLength":1,"type":"string"},"machine_id":{"description":"EXPERIMENT: the prepared machine to run this deploy on, from prepare_machine. Omit: the newest one this session has not used yet.","exclusiveMinimum":0,"type":"integer"},"media_mount_path":{"description":"Host mount path on the app VM/member. Redu mounts this into /var/www/html/wp-content/uploads.","type":"string"},"media_origin_url":{"description":"Public base URL where WordPress wp-content/uploads is served when using central_media_origin. Do not put this on the DB VM.","format":"uri","type":"string"},"media_space_flavor_id":{"description":"Flavor id for the media VM when create_media_space:true. Defaults to m1.small/backend default.","minLength":1,"type":"string"},"media_space_id":{"description":"Existing Redu media space id to mount at WordPress wp-content/uploads. Get it from list_media_spaces.","exclusiveMinimum":0,"type":"integer"},"media_space_name":{"description":"Optional media space name when create_media_space:true. Defaults to <deployment-name>-media.","maxLength":63,"minLength":1,"type":"string"},"media_space_size_gb":{"description":"Media space data volume size in GB when create_media_space:true (default 20).","maximum":2048,"minimum":1,"type":"integer"},"name":{"description":"Name for the deployment / VM (lowercase letters, numbers, hyphens).","maxLength":63,"minLength":1,"type":"string"},"network_id":{"description":"Existing private network id - from check_deploy_prerequisites (auto-selects your default) or list_private_networks. Optional: if omitted, redu auto-selects your default network.","minLength":1,"type":"string"},"port":{"default":3000,"description":"REQUIRED in practice: the port the app actually listens on inside the container - pass the port plan_deploy detected / the Dockerfile EXPOSE / the framework default. redu probes THIS port for health, so a wrong value fails the deploy. Defaults to 3000 only if omitted (e.g. a static nginx app listens on 80 - pass 80). IGNORED when worker:true.","maximum":65535,"minimum":1,"type":"integer"},"qdrant":{"description":"Vector DB wiring: 'managed' = a managed Qdrant on the same private network, wired as QDRANT_URL/QDRANT_HOST/QDRANT_PORT/QDRANT_GRPC_PORT/QDRANT_API_KEY (data persists across redeploys). The app must send QDRANT_API_KEY as its api-key; every managed Qdrant requires one. You never call create_qdrant for an app.","enum":["none","managed"],"type":"string"},"qdrant_flavor_id":{"description":"managed Qdrant only: the Qdrant VM size (from list_flavors).","type":"string"},"redis":{"description":"Redis wiring (auto-injects REDIS_URL/REDIS_HOST/REDIS_PORT/REDIS_PASSWORD, zero setup): 'managed' = a SEPARATE managed Redis VM on the same private network (data persists across app redeploys; reused on a same-name redeploy). You never call create_redis for an app.","enum":["none","managed"],"type":"string"},"redis_flavor_id":{"description":"managed Redis only: the dedicated Redis VM size (from list_flavors). Defaults to the app flavor; m1.small is plenty for a cache.","type":"string"},"redis_password":{"description":"managed Redis only: a specific password to set (otherwise auto-generated).","type":"string"},"redis_version":{"description":"managed Redis only: version (default '7').","enum":["7","6"],"type":"string"},"repo":{"description":"Filled in for you with the repository URL the USER gave (url_given_by_user in send_signals); the VM clones it. Otherwise omit it and pass source_token: the folder you work in is the source, and the platform refuses a repo it was not given. For a PRIVATE repo also pass git_token.","format":"uri","type":"string"},"runtime":{"description":"Informational: node/python/go/… (e.g. from plan_deploy).","type":"string"},"security_group_names":{"default":["default"],"items":{"type":"string"},"type":"array"},"source_token":{"description":"UPLOAD MODE: token from prepare_upload's curl step - deploys an uploaded tarball of your LOCAL working dir (no git, no PAT). Use this to deploy uncommitted code, a fixed clone of a repo you don't own, or private code. Omit `repo` when set.","minLength":16,"type":"string"},"subdir":{"description":"Build context within the source (e.g. 'demo-go') when the Containerfile/Dockerfile isn't at the root.","type":"string"},"worker":{"description":"HEADLESS WORKER / daemon mode. Deploys a long-running container with NO HTTP server, NO public URL, and NO health probe - ready = the container stays running. The VM still gets a floating IP + SSH stream (so you can SSH in), but nothing is exposed on the web. Use for background workers, a chaos-monkey, an on-infra agent runner, cron/queue consumers. `port`, `dname`, and the injected APP_URL/PUBLIC_URL are ignored. Run plan_deploy with worker:true too.","type":"boolean"}},"required":["name","keypair_name"],"type":"object"}Deploys a MULTI-CONTAINER app—- a repo that ships docker-compose.yml / compose.yaml—- onto ONE VM via podman-compose, and exposes ⟨169 unchanged words⟩
⟨488 unchanged words⟩ the app must be told its OWN url—- OAuth callbacks, cookie domain, a frontend that ⟨8 unchanged words⟩ yourself: `<label>-<8 lowercase letters/digits>.redu.cloud` (e.g. `myapp-7k2m9x4p.redu.cloud`)—- that exact form is used VERBATIM—- and wire the app's own URL env to ⟨104 unchanged words⟩ URL (a frontend that calls its API—- e.g. Lago's front -> API): GENERATE each ⟨24 unchanged words⟩ EXACT values. Do NOT use a bare `<label>.redu.cloud`—- redu appends a random suffix, so config ⟨29 unchanged words⟩ "flavor_id":{"description":"App VM size—- from list_flavors. A multi-container stack often wants m1.large+; ⟨40 unchanged words⟩ description":"REQUIRED. An EXISTING SSH keypair name—- from list_keypairs / import_keypair.","minLength":1, ⟨156 unchanged words⟩ -d` and BEFORE the app is marked ready—- redu runs `podman-compose run --rm <migrate_service|service> <cmd>` ( ⟨83 unchanged words⟩ "network_id":{"description":"Private network id—- auto-selected from check_deploy_prerequisites if omitted.","minLength": ⟨19 unchanged words⟩ side of its compose `ports:` mapping)—- redu health-probes + proxies THIS port, so ⟨263 unchanged words⟩ "description":"UPLOAD MODE: token from prepare_upload—- deploys an uploaded tarball of your LOCAL dir ⟨35 unchanged words⟩
⟨31 unchanged words⟩ on redu, over a single https endpoint—- no UDP, no extra ports. This is ⟨16 unchanged words⟩ GUARDRAIL: the first call does NOT deploy—- it returns the plan + hourly cost for ⟨62 unchanged words⟩
⟨149 unchanged words⟩ Auto-selected from your default private network if omitted—- the VMs you want to reach must be ⟨11 unchanged words⟩
⟨9 unchanged words⟩ to attach it to / detach it from—- from list_instances (id).","minLength":1 ⟨2 unchanged words⟩ "volume_id":{"description":"Volume to attach/detach—- from list_volumes (id). For a recovery, ⟨26 unchanged words⟩
⟨23 unchanged words⟩ "Log token for fetching controller logs (optional—- returned by this tool on first call)" ⟨6 unchanged words⟩
⟨127 unchanged words⟩ DEPLOYMENT VM (created by deploy_app/deploy_compose) pass keypair_name—- read it from get_deployment—- so the command uses `-i ~/.ssh/<keypair_name>` and authenticates ⟨43 unchanged words⟩
Lists your managed ClickHouse databases (OLAP / analytics—- its own resource, not a relational DB) ⟨21 unchanged words⟩
⟨14 unchanged words⟩ private-network connection details (private_ip, port 6379)—- connect from another instance on the same private ⟨9 unchanged words⟩
⟨182 unchanged words⟩ rates, and checks they FIT your quota—- so a plan that can't provision is caught ⟨204 unchanged words⟩ works (node, python, go, ...)—- it deploys as a container, so the ⟨86 unchanged words⟩ like cal.com/cal.diy, Rails, Django, JVM/Java apps)—- it sizes onto a memory-optimized SMALL-DISK flavor ( ⟨150 unchanged words⟩ the merged redu_md_markdown). They are SEPARATE files—- even if your own memory/notes from a prior ⟨16 unchanged words⟩
before
{"$schema":"http://json-schema.org/draft-07/schema#","additionalProperties":false,"properties":{"app_flavor":{"description":"Preferred app VM size (default m1.medium). Resolved against the real flavor list.","type":"string"},"app_profile":{"default":"generic","description":"Detected app profile. Set wordpress/woocommerce when source inspection finds wp-config.php, wp-content, WordPress/WooCommerce packages/plugins, or wordpress/woocommerce compose images. This is used for cluster safety.","enum":["generic","wordpress","woocommerce"],"type":"string"},"clickhouse_flavor":{"default":"m1.medium","description":"Preferred managed ClickHouse VM size (default m1.medium, a tier above the other datastores, which is what plan_managed_datastore also picks for ClickHouse). Resolved against the real flavor list.","type":"string"},"cluster_media_mode":{"default":"local_uploads","description":"For WordPress/WooCommerce cluster_target:true, use media_space as the default real fix: Redu creates/reuses an NFS media VM backed by a volume and mounts it at wp-content/uploads. central_media_origin is only for an already-configured external origin. local_uploads is refused at cluster time.","enum":["local_uploads","central_media_origin","media_space"],"type":"string"},"cluster_target":{"default":false,"description":"Set TRUE when the user asked to deploy this app as an autoscaling cluster. For WordPress/WooCommerce this requires managed MySQL/MariaDB plus a Redu media space mounted at wp-content/uploads (or an explicitly configured central_media_origin); local uploads are unsafe.","type":"boolean"},"compose_yaml":{"description":"PASTE the raw contents of the repo's docker-compose.yml / compose.yaml if it has one. redu scans it server-side for known deploy-breaking patterns (Docker-only `local` log driver, depends_on:service_healthy, profiles:, image+build, service count) and returns preemptive fixes BEFORE anything is provisioned. Costs you nothing to include; prevents whole classes of first-deploy failure.","maxLength":20000,"type":"string"},"containerizable":{"default":true,"description":"Whether the app can run in a container. Almost always true (a container is the delivery unit); set false ONLY if the app fundamentally can't be containerized — then redu can't deploy it yet.","type":"boolean"},"create_media_space":{"default":false,"description":"For WordPress/WooCommerce cluster_target:true: set TRUE when no suitable media space exists. The plan will include a small media VM + attached volume and deploy_app/deploy_compose will create it.","type":"boolean"},"db_engine":{"description":"Managed DB engine to plan. For WordPress/WooCommerce clusters use mariadb or mysql.","enum":["postgres","mysql","mariadb"],"type":"string"},"db_flavor":{"description":"Preferred managed Postgres VM size (default m1.small). Resolved against the real flavor list.","type":"string"},"deploy_mode":{"description":"OMIT by default: deploy end to end with no questions asked. 'guided' ONLY when the user explicitly said they want to approve or to be specific about something: the user approves the plan, then you proceed with its defaults. Sticky for the session once set here or via check_deploy_prerequisites.","enum":["guided","yolo"],"type":"string"},"dockerfile":{"description":"PASTE the raw contents of the repo's Dockerfile if it has one. redu scans it server-side for BuildKit heredocs, empty `EXPOSE ${ARG}`, and source-build fetches (go/cargo/npm/pip/apt) that are known to fail, and returns preemptive fixes.","maxLength":20000,"type":"string"},"has_container":{"default":false,"description":"Whether the repo already has a Dockerfile / compose file.","type":"boolean"},"heavy_build":{"description":"Set TRUE when the BUILD (not the running app) is resource-heavy — the container is built ON the VM today, so a build that needs more RAM/CPU than the idle app must be sized up or it OOMs mid-build. Signals: compiled-from-source native code (Rust/C++/CGO/Go-from-source), a monorepo/turborepo build, a Node build with a large heap (NODE_OPTIONS=--max-old-space-size), or bundling Chromium. When true, the plan raises the app VM to a build-capable floor (≥ m1.large) so `deploy_app`'s on-VM build doesn't get OOM-killed. The default m1.medium (4 GB) builds light apps fine but OOMs heavy ones.","type":"boolean"},"media_mount_path":{"default":"/mnt/redu-media/uploads","description":"Host mount path on app members for the shared uploads filesystem. Redu mounts this into /var/www/html/wp-content/uploads.","type":"string"},"media_origin_url":{"description":"Public base URL where WordPress wp-content/uploads is served when cluster_media_mode:'central_media_origin'. Do not use the DB VM as this origin.","format":"uri","type":"string"},"media_space_flavor":{"default":"m1.small","description":"Preferred media VM size when create_media_space:true. Resolved against list_flavors.","type":"string"},"media_space_id":{"description":"Existing Redu media space id to reuse for WordPress/WooCommerce cluster uploads. Get it from list_media_spaces. Omit when create_media_space:true.","exclusiveMinimum":0,"type":"integer"},"media_space_size_gb":{"default":20,"description":"Size of the media space data volume in GB when create_media_space:true.","maximum":2048,"minimum":1,"type":"integer"},"memory_heavy":{"description":"Set TRUE for a RAM-forward RUNNING app whose persistent state lives in a MANAGED DB / external store (NOT on the VM's local disk) — so it needs lots of memory but only a lean disk. Sizes onto a memory-optimized SMALL-DISK flavor (m1.mem16 = 4 vCPU / 16 GB / 40 GB; pass app_flavor:'m1.mem32' for 32 GB): the SAME RAM as the big standard flavors but a 40 GB disk instead of 160 GB — so it costs less AND snapshots/clusters FAR faster (a 40 GB snapshot is ~a third the time of 160 GB), which matters a lot if you'll scale the app into a cluster. Signals: heavy web frameworks (Next.js — e.g. cal.com / cal.diy — Rails, Django), JVM/Java apps, apps running several in-process services, or anything holding large working sets in memory, AND it uses managed Postgres/MySQL (needs_postgres with single_vm:false) or an external DB so the VM disk is just the app image. Do NOT set it if the app stores lots of data on LOCAL disk (user uploads, an on-VM database, large on-disk caches) — that needs a bigger standard-flavor disk. Distinct from heavy_build (BUILD-time RAM); mem16's 16 GB also covers most heavy builds, so the two compose.","type":"boolean"},"needs_clickhouse":{"default":false,"description":"App uses ClickHouse: the ANALYTICS / EVENTS store, a separate tier from the app's Postgres/MySQL. Set TRUE on ANY signal: a clickhouse/clickhouse-server image in the compose, a CLICKHOUSE_* / CLICKHOUSE_URL / CH_ env var, a clickhouse client dep (clickhouse-connect, @clickhouse/client, clickhouse-driver, clickhouse-go), or an `ON CLUSTER` / MergeTree migration. Analytics-shaped products (Plausible, PostHog, Langfuse, Matomo, SigNoz) run BOTH a relational DB for config AND ClickHouse for every event, so needs_postgres and needs_clickhouse are usually both true. Leaving it false when the app needs it under-quotes the user by a WHOLE datastore: measured 2026-08-07 on an analytics app, GBP 53.29/mo quoted against GBP 65.99/mo real, because the events tier was simply absent from the plan.","type":"boolean"},"needs_compose":{"default":false,"description":"Set TRUE when the repo ships a docker-compose.yml / compose.yaml that runs MULTIPLE services (app + its own db/redis/worker containers) as a stack — then deploy with deploy_compose (podman-compose on ONE VM), NOT deploy_app. A single-service compose that just wraps one Dockerfile can still use deploy_app. plan_deploy raises the app VM to a build-capable floor since a multi-container stack needs more RAM.","type":"boolean"},"needs_postgres":{"default":false,"description":"App uses Postgres (deps pg/prisma/sqlalchemy/drizzle or DATABASE_URL).","type":"boolean"},"needs_redis":{"default":false,"description":"App uses Redis (deps redis/ioredis/bullmq/celery or REDIS_URL).","type":"boolean"},"needs_vector_db":{"default":false,"description":"App uses a vector DB (deps qdrant-client / langchain+embeddings or QDRANT_URL).","type":"boolean"},"port":{"description":"Port the app's HTTP server listens on (PORT env / framework default / Dockerfile EXPOSE of a real app port — NOT a debug/IPC port). Omit if the repo has no HTTP server.","exclusiveMinimum":0,"type":"integer"},"redu_md":{"description":"PASTE the raw contents of `redu.md` from the repo root if the file exists (just `cat redu.md`). ⚠️ Only needed when you have NO source_token (a git-mode deploy): with a token, redu reads this file out of your upload and pasting it wastes 20-29 KB of output. You do NOT need to read or interpret it - redu parses it server-side and returns only a short digest. This is how a REDEPLOY keeps the SAME public URL and reuses the recorded fixes instead of rediscovering them.","maxLength":60000,"type":"string"},"runtime":{"description":"Detected app language/runtime (node, python, go, ruby, ...). Informational — the app deploys as a container, so the language does not gate the deploy.","minLength":1,"type":"string"},"serves_http":{"description":"Whether the repo actually RUNS an HTTP server that listens on a port. Set FALSE for a library, CLI tool, or language runtime that has no web server (e.g. a package you `import`, or a `bun`/`node`-style runtime) — redu can only deploy web services, so a non-server repo is reported as not-a-web-service instead of being costed. Note: an `EXPOSE` of a debug/IPC port (Chrome DevTools 9222/9242, metrics) is NOT an HTTP listener — set false. Leave unset only if you're confident it serves HTTP.","type":"boolean"},"single_vm":{"default":false,"description":"Force everything onto a single VM if the user prefers.","type":"boolean"},"source_token":{"description":"The `source_token` returned by your prepare_upload upload. ⚡ PREFER THIS over pasting: redu reads redu.md, the compose file and the Dockerfile straight out of the upload you ALREADY made, so you do not have to emit a 20-29 KB file as tool input. Run prepare_upload FIRST, then pass the token here. The upload is NOT consumed by this: it stays deployable. Anything you also paste below wins over what is read from the upload.","type":"string"},"start":{"description":"How the app starts (package.json start script / Procfile / Dockerfile CMD).","type":"string"},"vm_count":{"default":1,"description":"Number of app VMs (microservices). v1: usually 1.","maximum":10,"minimum":1,"type":"integer"},"worker":{"description":"HEADLESS WORKER / daemon: the repo runs a long-lived process with NO HTTP server (a chaos-monkey, a background/queue worker, an on-infra agent runner). Set TRUE to plan a WORKER deploy — a private VM with SSH access but NO public URL and NO health probe. serves_http/port are then not required (a serves_http:false repo is NOT rejected). Deploy it with deploy_app worker:true.","type":"boolean"}},"required":["runtime"],"type":"object"}after
{"$schema":"http://json-schema.org/draft-07/schema#","additionalProperties":false,"properties":{"app_flavor":{"description":"Preferred app VM size (default m1.medium). Resolved against the real flavor list.","type":"string"},"app_profile":{"default":"generic","description":"Detected app profile. Set wordpress/woocommerce when source inspection finds wp-config.php, wp-content, WordPress/WooCommerce packages/plugins, or wordpress/woocommerce compose images. This is used for cluster safety.","enum":["generic","wordpress","woocommerce"],"type":"string"},"clickhouse_flavor":{"default":"m1.medium","description":"Preferred managed ClickHouse VM size (default m1.medium, a tier above the other datastores, which is what plan_managed_datastore also picks for ClickHouse). Resolved against the real flavor list.","type":"string"},"cluster_media_mode":{"default":"local_uploads","description":"For WordPress/WooCommerce cluster_target:true, use media_space as the default real fix: Redu creates/reuses an NFS media VM backed by a volume and mounts it at wp-content/uploads. central_media_origin is only for an already-configured external origin. local_uploads is refused at cluster time.","enum":["local_uploads","central_media_origin","media_space"],"type":"string"},"cluster_target":{"default":false,"description":"Set TRUE when the user asked to deploy this app as an autoscaling cluster. For WordPress/WooCommerce this requires managed MySQL/MariaDB plus a Redu media space mounted at wp-content/uploads (or an explicitly configured central_media_origin); local uploads are unsafe.","type":"boolean"},"compose_yaml":{"description":"PASTE the raw contents of the repo's docker-compose.yml / compose.yaml if it has one. redu scans it server-side for known deploy-breaking patterns (Docker-only `local` log driver, depends_on:service_healthy, profiles:, image+build, service count) and returns preemptive fixes BEFORE anything is provisioned. Costs you nothing to include; prevents whole classes of first-deploy failure.","maxLength":20000,"type":"string"},"containerizable":{"default":true,"description":"Whether the app can run in a container. Almost always true (a container is the delivery unit); set false ONLY if the app fundamentally can't be containerized - then redu can't deploy it yet.","type":"boolean"},"create_media_space":{"default":false,"description":"For WordPress/WooCommerce cluster_target:true: set TRUE when no suitable media space exists. The plan will include a small media VM + attached volume and deploy_app/deploy_compose will create it.","type":"boolean"},"db_engine":{"description":"Managed DB engine to plan. For WordPress/WooCommerce clusters use mariadb or mysql.","enum":["postgres","mysql","mariadb"],"type":"string"},"db_flavor":{"description":"Preferred managed Postgres VM size (default m1.small). Resolved against the real flavor list.","type":"string"},"deploy_mode":{"description":"OMIT by default: deploy end to end with no questions asked. 'guided' ONLY when the user explicitly said they want to approve or to be specific about something: the user approves the plan, then you proceed with its defaults. Sticky for the session once set here or via check_deploy_prerequisites.","enum":["guided","yolo"],"type":"string"},"dockerfile":{"description":"PASTE the raw contents of the repo's Dockerfile if it has one. redu scans it server-side for BuildKit heredocs, empty `EXPOSE ${ARG}`, and source-build fetches (go/cargo/npm/pip/apt) that are known to fail, and returns preemptive fixes.","maxLength":20000,"type":"string"},"has_container":{"default":false,"description":"Whether the repo already has a Dockerfile / compose file.","type":"boolean"},"heavy_build":{"description":"Set TRUE when the BUILD (not the running app) is resource-heavy - the container is built ON the VM today, so a build that needs more RAM/CPU than the idle app must be sized up or it OOMs mid-build. Signals: compiled-from-source native code (Rust/C++/CGO/Go-from-source), a monorepo/turborepo build, a Node build with a large heap (NODE_OPTIONS=--max-old-space-size), or bundling Chromium. When true, the plan raises the app VM to a build-capable floor (≥ m1.large) so `deploy_app`'s on-VM build doesn't get OOM-killed. The default m1.medium (4 GB) builds light apps fine but OOMs heavy ones.","type":"boolean"},"media_mount_path":{"default":"/mnt/redu-media/uploads","description":"Host mount path on app members for the shared uploads filesystem. Redu mounts this into /var/www/html/wp-content/uploads.","type":"string"},"media_origin_url":{"description":"Public base URL where WordPress wp-content/uploads is served when cluster_media_mode:'central_media_origin'. Do not use the DB VM as this origin.","format":"uri","type":"string"},"media_space_flavor":{"default":"m1.small","description":"Preferred media VM size when create_media_space:true. Resolved against list_flavors.","type":"string"},"media_space_id":{"description":"Existing Redu media space id to reuse for WordPress/WooCommerce cluster uploads. Get it from list_media_spaces. Omit when create_media_space:true.","exclusiveMinimum":0,"type":"integer"},"media_space_size_gb":{"default":20,"description":"Size of the media space data volume in GB when create_media_space:true.","maximum":2048,"minimum":1,"type":"integer"},"memory_heavy":{"description":"Set TRUE for a RAM-forward RUNNING app whose persistent state lives in a MANAGED DB / external store (NOT on the VM's local disk) - so it needs lots of memory but only a lean disk. Sizes onto a memory-optimized SMALL-DISK flavor (m1.mem16 = 4 vCPU / 16 GB / 40 GB; pass app_flavor:'m1.mem32' for 32 GB): the SAME RAM as the big standard flavors but a 40 GB disk instead of 160 GB - so it costs less AND snapshots/clusters FAR faster (a 40 GB snapshot is ~a third the time of 160 GB), which matters a lot if you'll scale the app into a cluster. Signals: heavy web frameworks (Next.js - e.g. cal.com / cal.diy - Rails, Django), JVM/Java apps, apps running several in-process services, or anything holding large working sets in memory, AND it uses managed Postgres/MySQL (needs_postgres with single_vm:false) or an external DB so the VM disk is just the app image. Do NOT set it if the app stores lots of data on LOCAL disk (user uploads, an on-VM database, large on-disk caches) - that needs a bigger standard-flavor disk. Distinct from heavy_build (BUILD-time RAM); mem16's 16 GB also covers most heavy builds, so the two compose.","type":"boolean"},"needs_clickhouse":{"default":false,"description":"App uses ClickHouse: the ANALYTICS / EVENTS store, a separate tier from the app's Postgres/MySQL. Set TRUE on ANY signal: a clickhouse/clickhouse-server image in the compose, a CLICKHOUSE_* / CLICKHOUSE_URL / CH_ env var, a clickhouse client dep (clickhouse-connect, @clickhouse/client, clickhouse-driver, clickhouse-go), or an `ON CLUSTER` / MergeTree migration. Analytics-shaped products (Plausible, PostHog, Langfuse, Matomo, SigNoz) run BOTH a relational DB for config AND ClickHouse for every event, so needs_postgres and needs_clickhouse are usually both true. Leaving it false when the app needs it under-quotes the user by a WHOLE datastore: measured 2026-08-07 on an analytics app, GBP 53.29/mo quoted against GBP 65.99/mo real, because the events tier was simply absent from the plan.","type":"boolean"},"needs_compose":{"default":false,"description":"Set TRUE when the repo ships a docker-compose.yml / compose.yaml that runs MULTIPLE services (app + its own db/redis/worker containers) as a stack - then deploy with deploy_compose (podman-compose on ONE VM), NOT deploy_app. A single-service compose that just wraps one Dockerfile can still use deploy_app. plan_deploy raises the app VM to a build-capable floor since a multi-container stack needs more RAM.","type":"boolean"},"needs_postgres":{"default":false,"description":"App uses Postgres (deps pg/prisma/sqlalchemy/drizzle or DATABASE_URL).","type":"boolean"},"needs_redis":{"default":false,"description":"App uses Redis (deps redis/ioredis/bullmq/celery or REDIS_URL).","type":"boolean"},"needs_vector_db":{"default":false,"description":"App uses a vector DB (deps qdrant-client / langchain+embeddings or QDRANT_URL).","type":"boolean"},"port":{"description":"Port the app's HTTP server listens on (PORT env / framework default / Dockerfile EXPOSE of a real app port - NOT a debug/IPC port). Omit if the repo has no HTTP server.","exclusiveMinimum":0,"type":"integer"},"redu_md":{"description":"PASTE the raw contents of `redu.md` from the repo root if the file exists (just `cat redu.md`). ⚠️ Only needed when you have NO source_token (a git-mode deploy): with a token, redu reads this file out of your upload and pasting it wastes 20-29 KB of output. You do NOT need to read or interpret it - redu parses it server-side and returns only a short digest. This is how a REDEPLOY keeps the SAME public URL and reuses the recorded fixes instead of rediscovering them.","maxLength":60000,"type":"string"},"runtime":{"description":"Detected app language/runtime (node, python, go, ruby, ...). Informational - the app deploys as a container, so the language does not gate the deploy.","minLength":1,"type":"string"},"serves_http":{"description":"Whether the repo actually RUNS an HTTP server that listens on a port. Set FALSE for a library, CLI tool, or language runtime that has no web server (e.g. a package you `import`, or a `bun`/`node`-style runtime) - redu can only deploy web services, so a non-server repo is reported as not-a-web-service instead of being costed. Note: an `EXPOSE` of a debug/IPC port (Chrome DevTools 9222/9242, metrics) is NOT an HTTP listener - set false. Leave unset only if you're confident it serves HTTP.","type":"boolean"},"single_vm":{"default":false,"description":"Force everything onto a single VM if the user prefers.","type":"boolean"},"source_token":{"description":"The `source_token` returned by your prepare_upload upload. ⚡ PREFER THIS over pasting: redu reads redu.md, the compose file and the Dockerfile straight out of the upload you ALREADY made, so you do not have to emit a 20-29 KB file as tool input. Run prepare_upload FIRST, then pass the token here. The upload is NOT consumed by this: it stays deployable. Anything you also paste below wins over what is read from the upload.","type":"string"},"start":{"description":"How the app starts (package.json start script / Procfile / Dockerfile CMD).","type":"string"},"vm_count":{"default":1,"description":"Number of app VMs (microservices). v1: usually 1.","maximum":10,"minimum":1,"type":"integer"},"worker":{"description":"HEADLESS WORKER / daemon: the repo runs a long-lived process with NO HTTP server (a chaos-monkey, a background/queue worker, an on-infra agent runner). Set TRUE to plan a WORKER deploy - a private VM with SSH access but NO public URL and NO health probe. serves_http/port are then not required (a serves_http:false repo is NOT rejected). Deploy it with deploy_app worker:true.","type":"boolean"}},"required":["runtime"],"type":"object"}⟨49 unchanged words⟩ a short-lived ticket minted from your MCP credential—- NO API key needed in the command and ⟨27 unchanged words⟩
⟨19 unchanged words⟩ ) on THEIR machine before deploying to redu—- to see it run / sanity-check the container. Requires local podman/podman-compose. It's a suggestion, not a gate—- skip it and go straight to deploy_app any ⟨23 unchanged words⟩
⟨62 unchanged words⟩ ":{"description":"App runtime (node/python/go/…)—- from plan_deploy.","minLength":1,"type": ⟨5 unchanged words⟩
⟨20 unchanged words⟩ each, classify by its EXPOSE + CMD—- never by directory name) and pass them ⟨63 unchanged words⟩
⟨35 unchanged words⟩ -name Containerfile`, open each). Be exhaustive—- a missed browser-desktop subdir is the #1 cause ⟨12 unchanged words⟩ the Dockerfile EXPOSEs (e.g. [5901,6901])—- pass them so the deploy publishes http_port, ⟨24 unchanged words⟩ , e.g. 6901 for an xfce noVNC desktop—- NOT a raw VNC/debug port).","maximum ⟨13 unchanged words⟩ OPENING its Dockerfile (EXPOSE + CMD/start script)—- NOT by the directory name. 'browser-desktop' = a ⟨147 unchanged words⟩
⟨57 unchanged words⟩ only the name in front of it changes. Pass stack_name + stack_id from list_clusters. TWO RULES, and getting either wrong wastes a ⟨233 unchanged words⟩
⟨79 unchanged words⟩ ","minLength":1,"type":"string"},"stack_name":{"description":"Cluster stack name, from list_clusters (stack_name).","minLength":1,"type":"string"}},"required":["stack_name","stack_id","domain"],"type":"object"}
⟨5 unchanged words⟩ ON or OFF for a MANAGED data service—- the toggle that create_backup (a one-off volume ⟨76 unchanged words⟩
⟨82 unchanged words⟩ at focused, single-PR, testable units of work—- add an endpoint, write tests for a module, fix a specific bug, add a UI page—- and is poor at vague/large tasks, design ⟨9 unchanged words⟩ one line each): `- [ ] **Title**—- short description *(agent-ready)*`—- the `- [ ]` checkbox, `**bold title**`, `—- ` separator, and `*(agent-ready)*` are ⟨32 unchanged words⟩ a long-lived token installed on the controller)—- agents use the user's existing Claude Max/Pro subscription ⟨47 unchanged words⟩ present the authorize command only once it's ready—- that way the user doesn't run it into ⟨38 unchanged words⟩
⟨19 unchanged words⟩ parallel, and open PRs. No VPN needed—- runs over HTTPS; the controller stays running after ⟨7 unchanged words⟩ a single checkbox line `- [ ] **Title**—- short description *(agent-ready)*` (the `- ⟨21 unchanged words⟩ 'No agent-ready tasks', the tasks are mis-formatted—- fix them to that exact format and retry.
⟨20 unchanged words⟩ the source/master VM you SSH into and change—- it is also the always-on baseline member that ⟨18 unchanged words⟩ the image the group boots future members from—- the SAME load balancer and SAME *.redu.cloud URL ⟨58 unchanged words⟩ this propagates it to the autoscaled members).Zero-downtimeIt ismeasuredlow-disruption, notaspirationalzero-downtime: measured, afullrollingrollupdateoffailedeveryaboutmember2% ofarequests,3-memberinclusteroneservedshort699/699burstrequestsatwitheachnomemberfailuresswap, and never dropped below amajority.majority of members. Never promise the user zero downtime. That holds ONLY if the app starts on ⟨47 unchanged words⟩
⟨102 unchanged words⟩ and scales them back down to zero. FLAVOR—- LEAD WITH A MEMORY-OPTIMIZED SMALL-DISK FLAVOR (m1.mem16/m1.mem32) ⟨31 unchanged words⟩ build and to add each member under load—- a 40 GB mem flavor is several times ⟨44 unchanged words⟩ place), even though it is extra work—- it pays off on every scale-out, and ⟨39 unchanged words⟩ flavor. At rest ONLY your source VM serves—- there is NO idle extra VM to pay ⟨39 unchanged words⟩ but at rest a SINGLE source VM serves—- and that source VM is a plain VM ⟨40 unchanged words⟩ , or use a separate always-on setup. BILLED—- at rest it costs just your source VM ⟨444 unchanged words⟩
⟨157 unchanged words⟩ run FROM THE APP VM (response restructure.mode='assisted')—- redu will not run an unattended dump against ⟨219 unchanged words⟩ "flavor_id":{"description":"Member VM size—- from list_flavors. Every autoscaling member uses this flavor. ⟨56 unchanged words⟩ SOURCE VM being on a mem flavor too—- if it is on a big-disk flavor, ⟨65 unchanged words⟩ the user's intent is availability rather than load-scaling—- do not make them discover it. It runs ⟨200 unchanged words⟩ "keypair_name":{"description":"SSH keypair name—- from list_keypairs.","minLength":1,"type": ⟨199 unchanged words⟩ "network_id":{"description":"Private network id—- from list_private_networks. Use the SAME network the app's ⟨36 unchanged words⟩ source VM must actually serve on this port—- the load balancer health-checks it.","maximum": ⟨183 unchanged words⟩
⟨16 unchanged words⟩ A green result means the key actually works—- not merely that one is configured. Use this ⟨5 unchanged words⟩
⟨56 unchanged words⟩ database OFF the VM first, then cluster.FULLY AUTO forFor a single_vm Postgres (redu deployed it):redutheprovisionsFULLYaAUTOMATICmanagedmovePostgresisonSWITCHED OFF by thecluster'splatformprivatefornetworknow:andthe call is refused with 409 auto_restructure_unavailable (done automatically it could lose writes in rare cases). Tell themembersuser,migratethen move the database first: create a managed database, copy the data+intorepointit, point the apponatbootit—andnoredeploy,manualthenstepsupgrade with confirm_stateless:true. (responseWhenrestructure.mode='auto'it is switched on it needs high_availability:true.).For a compose/unknown DB, ASSISTED: redu provisions ⟨503 unchanged words⟩ the default mode), and the source VMleavesis retired once every member passes the loadbalancerbalancer'stohealthbecomechecktheandmachinenoyouotherdeploywebchangesaddressto(a custom domain,soanother port) still points at it; otherwise, or if the cluster is deleted meanwhile, itkeepsisrunningkept serving, still billed, and the user isbilledtoldtoo.why. Default false = load-scaling mode: the single ⟨512 unchanged words⟩
⟨62 unchanged words⟩ works: a full backup is taken first (keptawhateversafetyhappenscopy while the upgrade runs, deleted when it ends: the old machine is never touched and stays the restore point), the cluster is built alongside, the ⟨75 unchanged words⟩ , and the old single machine keeps billing for 3 days, untiltheyitdeleteisit;removed automatically; (2) WRITE LOSS, anything written to ⟨188 unchanged words⟩
Delete an API key (needs approval) — Deletes one of the account's API keys, by id or by name. Anything that uses the key stops working at once, so the first call deletes nothing: it shows the key (name, scopes, last used) for the user to approve; call again with confirm:true only after their yes. Use it to clean up a key you created for a task that is finished.
List the account's API keys — Lists the account's API keys: id, name, scopes, when each was created and last used, and when it expires. Never the secret (redu keeps only a hash). Use it to find a key to delete with delete_api_key, or to check what keys exist before minting another.
⟨9 unchanged words⟩ ,"id":{"type":"number"},"kept_stores":{},"managed_db":{},"mode":{"type":" ⟨6 unchanged words⟩
⟨44 unchanged words⟩ PORT must be the real app listen port.To wireDATASTORESaAREDB,MANAGEDpassBYdatabaseDEFAULT:'managed'(dedicatedeach one you reported in send_signals runs as a manageddatastorestoreVMstartedonattheyoursamesignalprivateandnetwork,isreusedwiredonwithsame-namenoredeploy)settingsor(DATABASE_URLdatabase:'single_vm'andforPG*/MYSQL_*,PostgresREDIS_URL,onQDRANT_URLthewithappQDRANT_API_KEY);VM.withoutChooseadb_enginesignal, pass database:'managed' (db_engine 'postgres',default;or 'mysql'/'mariadb' for WordPress/Matomo/LAMP),managedredis:'managed'only).or qdrant:'managed'. For WordPress/WooCommerce cluster intent, do not use ⟨38 unchanged words⟩
⟨178 unchanged words⟩ DB wiring (auto-injects the connection env + DATABASE_URL—, zero setup):'single_vm' = Postgres ON the app VM (cheapest, data dies when the VM is replaced; Postgres only);'managed' = a SEPARATE managed-DB VMauto-provisioned + wiredon the same private network (data PERSISTS across app redeploys; reused on a same-name redeploy).;Omit'single_vm' = Postgres ON the app VM (ordata'none')diesforwhennotheDB.VMWithis'managed'replaced;youPostgresdo
⟨51 unchanged words⟩ is the HOST port for the exposed service.DBDATASTORES ARE MANAGED BY DEFAULT:'compose'eachusesone you reported in send_signals runs as a managed store; thestack'sstack keeps its owndbsettings,container;and'managed'itsprovisionsown copy of each store is replaced by aseparateforwarder of the same name to the managedPostgres/MySQL/MariaDBoneVM(the stack's own user, password andappendsdatabaseconnectioncreatedenv.there, its init scripts run once). Pass no database/redis/qdrant settings; 'compose' (the stack's own) only when the user asked for one VM. For WordPress/WooCommerce cluster intent, do not leave ⟨50 unchanged words⟩
⟨192 unchanged words⟩ ,"database":{"description":"DB mode:'compose' (default)'managed' =useatheSEPARATEcomposemanagedfile'sPostgres/MySQL/MariaDB VM. THE STACK KEEPS ITS OWNdbSETTINGS:serviceits own copy of the store (self-contained,anothingserviceextrarunningprovisionedthat store's image);'single_vm'is=
⟨345 unchanged words⟩ 'clickhouse' -> create_clickhouse and pass CLICKHOUSE_* env yourself.Vector-DBAneedsQdrantarevectorflaggedDB is managed like the other datastores (vector_engines in send_signals; qdrant:'managed' without a signal); pgvector lives in the managed Postgres. In a session with send_signals the platform sizes every machine by measurement,notsoprovisioned.heavy_build, memory_heavy and needs_compose change no size there. Any containerizable app works (node, python, ⟨293 unchanged words⟩
⟨3 unchanged words⟩ additionalProperties":false,"properties":{"app_flavor":{"default":"m1.medium","description":"Preferred app VM size (default m1.medium ⟨300 unchanged words⟩ ,"type":"boolean"},"db_engine":{"default":"postgres","description":"Managed DB engine to plan. For ⟨10 unchanged words⟩ ],"type":"string"},"db_flavor":{"default":"m1.small","description":"Preferred managed Postgres VM size ( ⟨1231 unchanged words⟩
⟨9 unchanged words⟩ Use this before create_database, create_relational_database, create_redis, create_clickhouse orcreate_clickhouse.create_qdrant (engine 'qdrant', the vector database). For app deployments,preferyoudeploy_appneeddatabasenone of these:'managed'sodatastoresplan_deployareincludesmanagedandbywiresdefault, so thedatastoreonesautomatically.you report in send_signals are started and wired for you. Pass ha:true to plan a highly available ⟨43 unchanged words⟩
⟨11 unchanged words⟩ ,"mysql","mariadb","redis","clickhouse","qdrant"],"type":"string"},"ha":{ ⟨104 unchanged words⟩
⟨15 unchanged words⟩ git checkout, set true to honor .gitignore (tartrackedonlyfilestrackedplus+untracked, non-ignoredfilesones). Either way a TRACKED file is never dropped, also one under vendor/, dist/, build/ or target/.","type":"boolean"},"project_dir":{ ⟨19 unchanged words⟩
⭐ CALL THIS INSTEAD OF POLLING.BlocksWaitsuntilup to 50 seconds per call (an MCP client cuts a call at about 60 s) for the deploymentreachesto reach a TERMINAL state (ready, build_failed, error or ⟨87 unchanged words⟩ build is simply still running, so call it again,optionally(awithtypicaladeploylargertakestimeout_s.2 to 5 calls). Only status decides success. On build_failed or error ⟨11 unchanged words⟩
⟨19 unchanged words⟩ :"integer"},"timeout_s":{"description":"How longSeconds to wait in this call before returning timed_out:true: at most 50 (the default);300a larger value waits 50,maxbecause600).anAMCPtypicalclientdeploycutsreachesareadycallinat90aboutto
Create managed Qdrant (vector database) — Provisions a managed Qdrant vector database on a dedicated VM. Requires a recent plan_managed_datastore (engine 'qdrant'). Reachable from your private network at <private_ip>:6333 (REST) and :6334 (gRPC), and at a public https URL; every request must carry the api-key header (always required). Backups: set_managed_backups with service 'qdrant'. FOR AN APP YOU DEPLOY YOU DO NOT NEED THIS: report vector_engines:['qdrant'] in send_signals and the deploy starts and wires a managed Qdrant itself (a compose stack's own qdrant service is replaced by it). Provisioning takes a few minutes; poll list_q…
Delete managed Qdrant — Deletes a managed Qdrant and its underlying VM, with every collection in it. Pass the numeric id from list_qdrant. This cannot be undone.
List managed Qdrant — Lists your managed Qdrant vector databases (create_qdrant, or started for a deploy). Once a row's status is 'ready' it carries private_ip (REST port 6333, gRPC 6334); the API key is never listed.
⟨43 unchanged words⟩ ,"request":{},"result":{},"source_mismatch":{"type":"boolean"},"validation":{}},"type":"object"}
⟨43 unchanged words⟩ ,"request":{},"result":{},"source_mismatch":{"type":"boolean"},"validation":{}},"type":"object"}
⟨43 unchanged words⟩ ,"request":{},"result":{},"source_mismatch":{"type":"boolean"},"validation":{}},"type":"object"}
⟨43 unchanged words⟩ ,"request":{},"result":{},"source_mismatch":{"type":"boolean"},"validation":{}},"type":"object"}
⟨43 unchanged words⟩ ,"request":{},"result":{},"source_mismatch":{"type":"boolean"},"validation":{}},"type":"object"}
⟨43 unchanged words⟩ ,"request":{},"result":{},"source_mismatch":{"type":"boolean"},"validation":{}},"type":"object"}
⟨43 unchanged words⟩ ,"request":{},"result":{},"source_mismatch":{"type":"boolean"},"validation":{}},"type":"object"}
⟨43 unchanged words⟩ ,"request":{},"result":{},"source_mismatch":{"type":"boolean"},"validation":{}},"type":"object"}
⟨43 unchanged words⟩ ,"request":{},"result":{},"source_mismatch":{"type":"boolean"},"validation":{}},"type":"object"}
⟨1225 unchanged words⟩ :"string"},"repo":{"description":"GITFilledMODE:inpublicforgityourepowith the repository URL the USER gave (httpsurl_given_by_user in send_signals).;FortheaVMPRIVATEclonesrepoit.alsoOtherwise omit it and passgit_token.source_token:Omitthewhenfolderusingyousource_tokenwork(uploadinmode).is the source, and the platform refuses a repo it was not given. For a PRIVATE repo also pass git_token.","format":"uri","type":"string ⟨170 unchanged words⟩
⟨43 unchanged words⟩ ,"request":{},"result":{},"source_mismatch":{"type":"boolean"},"validation":{}},"type":"object"}
⟨1130 unchanged words⟩ :"string"},"repo":{"description":"GITFilledMODE:inpublicforgityourepowith the repository URLthattheshipsUSERagavedocker-compose(url_given_by_userfile.in send_signals); the VM clones it. Otherwise omit it and pass source_token: the folder you work in is the source. Private repo also needs git_token.Omit when using source_token.","format":"uri","type":"string ⟨79 unchanged words⟩
⟨43 unchanged words⟩ ,"request":{},"result":{},"source_mismatch":{"type":"boolean"},"validation":{}},"type":"object"}
⟨43 unchanged words⟩ ,"request":{},"result":{},"source_mismatch":{"type":"boolean"},"validation":{}},"type":"object"}
⟨43 unchanged words⟩ ,"request":{},"result":{},"source_mismatch":{"type":"boolean"},"validation":{}},"type":"object"}
⟨43 unchanged words⟩ ,"request":{},"result":{},"source_mismatch":{"type":"boolean"},"validation":{}},"type":"object"}
⟨512 unchanged words⟩ markdown report (Mermaid diagram + cost) tosaveSHOWastheredu-deploy-plan.mduserandonlyshowintheguideduser.mode; write no file from it. Every deploy leaves TWO MANDATORY files at the repo root with DIFFERENT purposes, both written ONCE at the end from the deployment that actually ran (get_deployment), after you verified it works: redu-deploy-plan.md =THISwhatrun'sthisplan/estimaterun deployed, and redu.md = the DURABLE deploy memory ⟨83 unchanged words⟩
⟨359 unchanged words⟩ :"string"},"deploy_mode":{"description":"guidedOMIT(by default):=deployONEendapprovaltogate:endthewithusernoapprovesquestionstheasked.plan,'guided'thenONLYyouwhenproceed
⟨43 unchanged words⟩ ,"request":{},"result":{},"source_mismatch":{"type":"boolean"},"validation":{}},"type":"object"}
⟨43 unchanged words⟩ ,"request":{},"result":{},"source_mismatch":{"type":"boolean"},"validation":{}},"type":"object"}
⟨43 unchanged words⟩ ,"request":{},"result":{},"source_mismatch":{"type":"boolean"},"validation":{}},"type":"object"}
⟨21 unchanged words⟩ ,"invalid_keypair_name":{"type":"boolean"},"machine":{},"mode":{"type":"string"},"needs_keypair" ⟨19 unchanged words⟩
⟨21 unchanged words⟩ ,"invalid_keypair_name":{"type":"boolean"},"machine":{},"mode":{"type":"string"},"needs_keypair" ⟨19 unchanged words⟩
⟨21 unchanged words⟩ ,"invalid_keypair_name":{"type":"boolean"},"machine":{},"mode":{"type":"string"},"needs_keypair" ⟨19 unchanged words⟩
⟨21 unchanged words⟩ ,"invalid_keypair_name":{"type":"boolean"},"machine":{},"mode":{"type":"string"},"needs_keypair" ⟨19 unchanged words⟩
⟨21 unchanged words⟩ ,"invalid_keypair_name":{"type":"boolean"},"machine":{},"mode":{"type":"string"},"needs_keypair" ⟨19 unchanged words⟩
⟨21 unchanged words⟩ ,"invalid_keypair_name":{"type":"boolean"},"machine":{},"mode":{"type":"string"},"needs_keypair" ⟨19 unchanged words⟩
⟨21 unchanged words⟩ ,"invalid_keypair_name":{"type":"boolean"},"machine":{},"mode":{"type":"string"},"needs_keypair" ⟨19 unchanged words⟩
⟨21 unchanged words⟩ ,"invalid_keypair_name":{"type":"boolean"},"machine":{},"mode":{"type":"string"},"needs_keypair" ⟨19 unchanged words⟩
⟨681 unchanged words⟩ ,"type":"object"},"flavor_id":{"default":"3","description":"Instance size — from list_flavors. Default ⟨138 unchanged words⟩ first.","minLength":1,"type":"string"},"machine_id":{"description":"EXPERIMENT: the prepared machine to run this deploy on, from prepare_machine. Omit: the newest one this session has not used yet.","exclusiveMinimum":0,"type":"integer"},"media_mount_path":{"description":"Host mount ⟨559 unchanged words⟩
⟨21 unchanged words⟩ ,"invalid_keypair_name":{"type":"boolean"},"machine":{},"mode":{"type":"string"},"needs_keypair" ⟨19 unchanged words⟩
⟨639 unchanged words⟩ ,"type":"array"},"flavor_id":{"default":"3","description":"App VM size — from list_flavors. ⟨57 unchanged words⟩ import_keypair.","minLength":1,"type":"string"},"machine_id":{"description":"EXPERIMENT: the prepared machine to run this deploy on, from prepare_machine. Omit: the newest one this session has not used yet.","exclusiveMinimum":0,"type":"integer"},"media_mount_path":{"description":"Host mount ⟨494 unchanged words⟩
⟨21 unchanged words⟩ ,"invalid_keypair_name":{"type":"boolean"},"machine":{},"mode":{"type":"string"},"needs_keypair" ⟨19 unchanged words⟩
⟨21 unchanged words⟩ ,"invalid_keypair_name":{"type":"boolean"},"machine":{},"mode":{"type":"string"},"needs_keypair" ⟨19 unchanged words⟩
⟨21 unchanged words⟩ ,"invalid_keypair_name":{"type":"boolean"},"machine":{},"mode":{"type":"string"},"needs_keypair" ⟨19 unchanged words⟩
⟨21 unchanged words⟩ ,"invalid_keypair_name":{"type":"boolean"},"machine":{},"mode":{"type":"string"},"needs_keypair" ⟨19 unchanged words⟩
⟨21 unchanged words⟩ ,"invalid_keypair_name":{"type":"boolean"},"machine":{},"mode":{"type":"string"},"needs_keypair" ⟨19 unchanged words⟩
⟨21 unchanged words⟩ ,"invalid_keypair_name":{"type":"boolean"},"machine":{},"mode":{"type":"string"},"needs_keypair" ⟨19 unchanged words⟩
⟨21 unchanged words⟩ ,"invalid_keypair_name":{"type":"boolean"},"machine":{},"mode":{"type":"string"},"needs_keypair" ⟨19 unchanged words⟩