Nine steps, each checkable before you go on. About an afternoon, and most of that is waiting for Google to enable things.
Have all of these on the machine you will operate from — macOS or Linux.
brew install --cask gcloud-cli on a Mac, otherwise cloud.google.com/sdk/docs/install. Then gcloud auth login, on a Google account that may create projectsbrew install node on a Mac, otherwise nodejs.orggit --version to checkbrew install gh on a Mac, otherwise cli.github.comNothing else to install. Every command below starts with npx, which fetches the tool for that one run:
npx @meffecta/agent version # names the engine build it deploys
Pin it once you are running — npx @meffecta/agent@1.0.0 rather than plain
npx. Each release of the tool deploys the engine image published alongside
it, so the version you run is how you choose your engine: deliberately, rather than by
surprise.
Nine steps. Each one is checkable before you go on.
Give the agent a project of its own, separate from any product project it will read: everything the agent runs as can read every password stored in this project, and your mail tokens should not sit where another system's machinery can reach them.
npx @meffecta/agent create-project
acme-agent-506513). The ID, not the display name, is what every later command needs.It lists the accounts you can use, marks closed ones as unselectable, links your choice, and then checks the project really is billed — because linking can report success and still leave it unbilled.
npx @meffecta/agent link-billing --project <PROJECT_ID>
A private GitHub repo holding everything about this deployment. Three lines make it and put you inside it, and the first is once ever — every command from here on runs from this folder, which is what says which deployment you mean.
gh auth login # once, ever — opens your browser gh repo create acme-agent-home --private --clone # creates it, copies it here cd acme-agent-home # move into the folder it made
Now fill the folder:
npx @meffecta/agent init
Four files, and anything already there is left alone:
deployment.env # which GCP project and service this is SYSTEM.md # who it works for — read at the start of every run systems/example.md # a file per system it may reach, and the variables jobs/example.md # a file per job: a few settings, then the prompt
That is a working repo. The two example files are the shape of the thing — rename them after something real, and delete what is left. What a job is and what a register entry is have the full shape of each.
Open SYSTEM.md now and say who the agent works for. It goes in front of every job's prompt, and a few honest sentences beat a page of instructions.
worlds/ is always a folder, and each thing inside it is one world — a business, product or brand the agent works on. A world holds what would otherwise be repeated in every job: who you sell to, what you may claim, who the competitors are. One world is either a single file (worlds/acme.md) or a folder of them (worlds/acme/) once there is more than a page of it. Most deployments have exactly one. Skip the whole thing to begin with; nothing depends on it.
Then fill in deployment.env. PROJECT is fixed — it is the ID Google handed you in step 1, and it has to match exactly. SERVICE and ARTIFACT_REPO are the opposite: nothing by those names exists yet, and you are inventing them. Anything you write works, and step 5 creates both under it. The examples below only keep them recognisable beside the project.
PROJECT=acme-agent-506513 # the project ID from step 1 — this one is given SERVICE=acme-agent # your name for the service the agent will run as ARTIFACT_REPO=acme-agent-images # your name for the store it gets the engine from REGION=europe-west1 # which Google data centre all of it lives in
Two more are optional. TIMEZONE is the zone every job's schedule is read in — leave it out and they run on UTC. ACCOUNT pins every command to one Google login, which matters only if you are signed into several.
The whole file is committed. None of it is secret — secrets live in Google's Secret Manager, added in step 6 — so a clone of this repo is everything the next person needs to operate the deployment.
Then send it all to GitHub. These same three commands are how every later change reaches the agent — a new job, a reworded prompt, a fact it should know:
git add -A # take everything you just made git commit -m "First set-up" # save it, with a note to your future self git push # send it to GitHub
Two, and the service will not start without either.
npx @anthropic-ai/claude-code setup-token # → CLAUDE_CODE_OAUTH_TOKEN, step 6
Mail, analytics, a CRM, a database — anything the agent reaches — comes later and one at a time: Systems.
Safe to run as many times as you like — re-run it after any change. It switches on the Google APIs, creates the registry the engine image is served from, the login the agent itself acts as, a bucket for its memory, a write-once bucket for the audit trail (kept a year), the task queue that carries work to it, and the empty service the engine will run in.
If you would rather see it before it does anything, ask it first. This changes nothing at all — it just prints everything it would create and every permission it would grant, in your project:
npx @meffecta/agent setup-infra --dry-run
Then, for real:
npx @meffecta/agent setup-infra
deployment.env — if not, you are in the wrong directory.Secrets go into Google's Secret Manager and are connected to the service in one step; the value is never shown on screen, and never saved in your command history. Re-running one rotates it.
npx @meffecta/agent set-secret AGENT_API_SECRET --random # generated for you npx @meffecta/agent set-secret CLAUDE_CODE_OAUTH_TOKEN # prompts, hidden npx @meffecta/agent set-secret GITHUB_TOKEN
Non-secret settings go through set-env. The service will not boot without GIT_REPO_URL:
npx @meffecta/agent set-env GIT_REPO_URL=https://github.com/<owner>/<repo>.git
That is everything the engine needs. Anything else — Gmail, analytics, logs, a CRM — is one credential each, added whenever you want the ability, not now.
Rolls out the engine build this version of the tool was published with, and sets the service's size and scaling from deployment.env — so a setting changed by hand in the console cannot quietly survive a deploy. --tag <version> goes to a different build, which is also how you roll back.
npx @meffecta/agent deploy --dry-run # see the target first
npx @meffecta/agent deploy
It then creates the triggers, because the service sleeps when nothing is happening and is only awake while answering a request — so nothing inside it can run on a timer of its own. One Cloud Scheduler job per cron: in your jobs, and a housekeeping sweep. It can only do this after the engine is running, since the running engine is the only thing that knows which jobs exist.
<service>-sweep, and npx @meffecta/agent triggers agrees.Every later rollout re-syncs them, so a changed cron: needs nothing extra. To re-sync without a rollout — the only time you need it on its own — run npx @meffecta/agent sync-triggers.
npx @meffecta/agent status npx @meffecta/agent jobs npx @meffecta/agent doctor
Seeing your jobs listed is what proves the service could read your content repo. An empty list means GIT_REPO_URL is wrong or GITHUB_TOKEN cannot clone it.
To see what all of this actually created inside Google Cloud — and what each piece is for, in plain words — there is one more:
npx @meffecta/agent resources
It lists everything, says which parts cost anything, and gives you a link straight into the Google Cloud console for each one. Nothing you have to remember the name of.
Then test every connection you have set up, from inside the deployment:
npx @meffecta/agent verify-credentials
skip only means you have not set that one up yet.The same thing has a web page, for anyone who would rather not use a terminal: open /ask on the service URL that status printed. Any username, the secret as the password.
The job init scaffolded touches nothing real. It reports who it works for, which systems it can and cannot reach, and where its memory is — which is every part of the path a real job depends on.
npx @meffecta/agent run example npx @meffecta/agent logs --limit 50
SYSTEM.md, and with the systems you have registered.Then write a real one. create-job writes the file from a sentence, and Jobs is the full reference. Read what the first one produces before giving it a cron:.
Everything about your deployment. Nothing about the engine.
acme-agent-home/ deployment.env # which GCP project and service your deployment is SYSTEM.md # the rules every job gets, before its own instructions jobs/ # one file per job morning-brief.md weekly-review.md systems/ # one file per system it can reach gmail.md google-analytics.md worlds/ # optional — what it should know about the business acme.md skills/ # optional — procedures of your own, or overrides accounting/SKILL.md
| deployment.env | Which GCP project, service, region and image repository this is. Committed on purpose — none of it is secret, so a clone of the repo is everything the next operator needs. |
| SYSTEM.md | Rules, not facts. It goes in front of every job: who the agent serves, how it should write, what it must never do. The engine adds its own baseline after yours, so the safety rules you cannot switch off stay on. It applies to every run including one working inside another repo. |
| jobs/*.md | A few settings, then a page of plain-language instructions — Jobs. |
| systems/*.md | The register. One file per system, naming the skill that reaches it, the variables holding its credential, what access is allowed, and the cheapest read that proves it works. A system with no file here is a system the agent does not have — the credential can be perfectly valid and still no job will ever use it. The shape of an entry. |
| worlds/ | One world per business, product or brand — each either a single file (worlds/acme.md) or a folder of them. Facts that outlive any one job, and that travel with the agent when a job runs inside one of your other repos. Every run is told which worlds exist. |
| skills/ | A procedure of your own. A folder named after one of the built-in skills replaces it — the folder name is what decides, so renaming one quietly stops it overriding anything. |
| CLAUDE.md | Optional, and different from the rest: claude loads it from the working directory by itself, so it reaches every run in this clone without the engine composing it. Keep it short and about the repo; rules belong in SYSTEM.md, which outranks it. |
| On push | Prompts, run settings, SYSTEM.md, systems/, worlds/, skills/. Live on the next run — there is nothing to build and nothing to deploy. |
| On deploy | Whether a job exists, and when it fires: a new job file, a renamed one, or a changed cron:, webhook:, inbox: or allowFrom:. These are read once when the service starts, so it has to restart to see them. |
| On engine deploy | The engine's own code and its built-in skills. Those ship in the image, so a new capability from us arrives when you choose to move versions. |
Renaming a job file makes a new job. Its memory is kept under its name, so the old name keeps the history and the new one starts blank — worth doing deliberately rather than while tidying up.