feat(skills): bundle build-monetized-app skill#7160
Conversation
Add a new bundled skill, `build-monetized-app`, that gives the agent
the recipe for building an app on Eliza Cloud that earns markup-share
revenue, deploying it as a container, and routing its earnings back
into hosting via the existing pay-as-you-go-from-earnings cron.
The skill exists because monetized apps are how a Milady-style
autonomous agent stays online: container hosting bills daily, the
container-billing cron pulls from `redeemable_earnings_ledger` before
`credit_transactions`, so any app that earns more than its hosting
cost keeps the agent alive without owner intervention. This is a
real product constraint, not theoretical — the survival-economics
reference walks through the exact accounting (cron at
`app/api/cron/container-billing/route.ts`).
Layout follows the existing convention used by `eliza-cloud` and
`eliza-app-development`:
packages/skills/skills/build-monetized-app/
SKILL.md # 4 KB — purpose, when-to-use,
# default flow, links to references
references/
sdk-flow.md # 4 KB — the 6-step
# @elizaos/cloud-sdk deploy + monetize flow
survival-economics.md # 4 KB — earnings/hosting loop, why this matters
failure-modes.md # 5 KB — recovery table for real failures
The frontmatter `name` matches the directory name (`build-monetized-app`)
and the `description` is written so the skill-eligibility selector
picks it up for build/deploy/monetize tasks without overlapping with
the broader `eliza-cloud` skill (which covers cloud as a backend in
general). The two skills are explicitly cross-referenced.
Verified live:
bot before: AgentSkills: Initialized with 31 skills (..., bundled: 29, ...)
bot after: AgentSkills: Initialized with 32 skills (..., bundled: 30, ...)
ensure-skills.mjs's `ensureShippedSkills()` discovered the new
directory and seeded it into `~/.eliza/skills/` on next run; the
AgentSkills service then registered it at boot. No loader changes
needed because the discovery path is already directory-driven.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| memory: 512, | ||
| env: { /* image-specific runtime vars */ }, | ||
| }, | ||
| }); | ||
| ``` | ||
|
|
||
| After `postApiV1Containers` returns, poll `getApiV1ContainersById(container.id)` until `status === "running"` and `load_balancer_url` is populated. Health-check failures here mean the image's server doesn't bind to `$PORT` correctly — pull `cloud.routes.getApiV1ContainersByIdLogs(container.id)` and surface to the human. | ||
|
|
||
| ## 4. Set markup | ||
|
|
There was a problem hiding this comment.
image vs ecr_image_uri field name conflict
The existing eliza-cloud/references/apps-and-containers.md describes the container deployment flow as: (1) get temporary ECR credentials, (2) push a Docker image, (3) create a container with ecr_image_uri. This skill's postApiV1Containers payload uses image instead. An agent choosing between these two skills will get different field names for the same call, and one of them will silently fail or be ignored by the API. If ecr_image_uri is the schema field, passing image will leave the image unset and the deployment will never start.
|
|
||
| - Listen on `$PORT` (cloud sets this at runtime) | ||
| - Expose a health-check endpoint |
There was a problem hiding this comment.
Broken reference path for
edad-chat
cloud-mini-apps/edad-chat/server.ts does not exist in this repository. An agent instructed to "copy its requestForwarder" will not be able to find the file. Either include the relevant snippet inline (or in a dedicated references/ file), or point to the correct path / external URL so the reference is actually followable.
|
|
||
| Container hosting is pay-as-you-go, billed daily. The cron at `app/api/cron/container-billing/route.ts` runs once per day and, for each container: | ||
|
|
||
| 1. Computes the day's hosting cost (CPU + RAM at the tier's per-second rate) |
There was a problem hiding this comment.
Cron path not resolvable from this repo
app/api/cron/container-billing/route.ts doesn't exist under this repo's tree (it lives in the private eliza/cloud sub-repo). The agent will fail to look it up if instructed to inspect the billing logic. The survival-economics.md doc would be more useful if it described the billing rules inline rather than directing the agent to a path it can't open.
Address feedback: the original SKILL.md mentioned a "shipped reference implementation" without a clickable link, which was thin. The canonical public reference lives at: https://github.com/elizaOS/cloud-mini-apps/tree/main/apps/edad-chat (public repo, latest commit 2026-04-27 refactored it to use @elizaos/cloud-sdk). Restore that as the primary external reference in SKILL.md, and explicitly point at it from references/sdk-flow.md as the canonical shape for the chat-forwarder. Skill remains self-contained — the inline minimal example in references/sdk-flow.md still works for agents that don't want to clone an external repo. The cloud-mini-apps reference is for agents that want full code.
|
|
||
| ```ts | ||
| const container = await cloud.routes.postApiV1Containers({ | ||
| json: { | ||
| image: `<registry>/<repo>:<tag>`, | ||
| appId, | ||
| cpu: 256, | ||
| memory: 512, | ||
| env: { /* image-specific runtime vars */ }, | ||
| }, | ||
| }); | ||
| ``` | ||
|
|
||
| After `postApiV1Containers` returns, poll `getApiV1ContainersById(container.id)` until `status === "running"` and `load_balancer_url` is populated. Health-check failures here mean the image's server doesn't bind to `$PORT` correctly — pull `cloud.routes.getApiV1ContainersByIdLogs(container.id)` and surface to the human. | ||
|
|
||
| ## 4. Set markup |
There was a problem hiding this comment.
Polled container result never captured —
container.load_balancer_url will be null in step 5
Step 3 creates the container into const container = ... and then instructs the agent to poll getApiV1ContainersById(container.id) until load_balancer_url is populated — but the polling result is never assigned to a variable. Step 5 then references container.load_balancer_url, which is still the value from the initial creation response, where load_balancer_url is null or undefined. An agent that follows this code literally will call patchApiV1AppsById with app_url: null and allowed_origins: [null], producing a 400 invalid_origin / 400 invalid_app_url error and leaving the OAuth redirect broken.
The fix is to assign the polled result, for example:
let running = container;
while (running.status !== "running" || !running.load_balancer_url) {
await new Promise(r => setTimeout(r, 5000));
running = await cloud.routes.getApiV1ContainersById(container.id);
}Then step 5 should use running.load_balancer_url instead of container.load_balancer_url.
…d-app feat(skills): bundle build-monetized-app skill
Summary
Add a new bundled skill —
build-monetized-app— that teaches the agent how to build a monetized app on Eliza Cloud, deploy it as a container, and route the earnings back into hosting via the existing pay-as-you-go-from-earnings cron.The skill exists because monetized apps are how a Milady-style autonomous agent stays online: container hosting bills daily, the container-billing cron pulls from
redeemable_earnings_ledgerbeforecredit_transactions, so any app that earns more than its hosting cost keeps the agent alive without owner intervention. This is a real product constraint that the agent should be aware of when picking what to build.What's in here
Layout follows the existing convention used by
eliza-cloudandeliza-app-development:SKILL.mdis small and contains: frontmatter (name + description), purpose, default flow, links to references, and explicit out-of-scope listWhy a separate skill rather than extending
eliza-cloudeliza-cloudis the broad "Cloud is your backend" skill — auth, APIs, redirects.build-monetized-appis the narrow "you are building an app to monetize and stay alive" skill. Different audience (agent-the-builder vs agent-the-consumer), different decisions (markup %, container tier, retry strategy on deploy failure), different mental model (survival-economics).The two are cross-referenced:
build-monetized-app/SKILL.mddefers toeliza-cloudfor auth-flow details. No duplicated content.Verification — live on running bot
Synced into a milady deployment. Before the new skill:
After:
ensure-skills.mjs's existingensureShippedSkills()walks the directory, seeded the new skill into~/.eliza/skills/build-monetized-app/on first run, and the AgentSkills service registered it at boot. No loader changes needed — the discovery path is already directory-driven.The frontmatter
namematches the directory name and thedescriptionis structured so the eligibility selector picks the skill for build/deploy/monetize tasks without overlapping witheliza-cloud.What's intentionally NOT in here
@elizaos/cloud-sdk. The skill teaches the agent to call the SDK directly. Wrappers add no value and rot when the SDK evolves.pay_as_you_go_from_earningstrue (default) or false. The earnings flow is described; the toggle is the owner's call.5-rule check
@elizaos/cloud-sdkdirectly.ensureShippedSkills()discovery (no new loader), references the existingeliza-cloudskill instead of re-explaining cloud auth.🤖 Generated with Claude Code
Greptile Summary
This PR adds a new bundled
build-monetized-appskill — four markdown files teaching the agent to register an Eliza Cloud app, build and push a container, configure markup, and hook into the earnings-funded hosting loop.sdk-flow.mdstep 3–5): The polling loop that waits forload_balancer_urlto become available never captures its result into a variable; step 5 then readscontainer.load_balancer_urlfrom the initial creation response, which isnull, causing thepatchApiV1AppsByIdcall to setapp_url: nulland breaking the OAuth redirect and CORS configuration for every deployment.Confidence Score: 4/5
Safe to merge after fixing the missing polling-result variable in sdk-flow.md step 3; the P1 causes every agent-driven deploy to silently set a null app_url.
One P1 finding (stale container variable in the step 3→5 transition) sets the ceiling at 4. The remaining findings are P2 style/robustness suggestions that do not block merging.
packages/skills/skills/build-monetized-app/references/sdk-flow.md — the polling-result assignment gap between step 3 and step 5.
Important Files Changed
Sequence Diagram
sequenceDiagram participant Agent participant CloudSDK as cloud-sdk participant Registry as Container Registry Agent->>CloudSDK: postApiV1Apps(name, placeholder_url) CloudSDK-->>Agent: app.id, apiKey Agent->>Registry: docker build + push image Registry-->>Agent: image tag ready Agent->>CloudSDK: postApiV1Containers(image, appId, cpu, memory) CloudSDK-->>Agent: container(id, status=pending, load_balancer_url=null) loop Poll until running Agent->>CloudSDK: getApiV1ContainersById(id) CloudSDK-->>Agent: container(status, load_balancer_url) end Note over Agent: polled result not stored - container.load_balancer_url remains null Agent->>CloudSDK: patchApiV1AppsById(inference_markup_percentage=20) CloudSDK-->>Agent: markup set Agent->>CloudSDK: patchApiV1AppsById(app_url=null, allowed_origins=[null]) CloudSDK-->>Agent: 400 invalid_originReviews (2): Last reviewed commit: "fix(skill/build-monetized-app): point at..." | Re-trigger Greptile