# Meffecta Agent > A self-hosted Claude agent that runs as a job runner in your own Google Cloud project. Jobs > are Markdown files in a private Git repo you own; triggers are a schedule, a webhook, an > incoming email, or the agent handing work to a later run of itself. The engine is a public > container image (ghcr.io/meffecta/agent) and holds no company's data; everything about your > deployment lives in your own "content repo". Operated with one CLI: `npx @meffecta/agent`. This file exists because a large part of the audience for this site is an AI agent doing the installation on someone's behalf. Every page is complete on its own; this is the index. ## Pages - [Overview](https://agent.meffecta.com/): what it is, ten worked examples of what to point it at (a morning brief; an address you can write to; a researched reply to every new lead; meeting prep; chasing overdue invoices; a ninety-day warning on every contract that auto-renews; the promises you made in email; a quiet-customer watch; a weekly marketing report; a monthly bookkeeping pass), the engine/content split, what a run is bounded by, what it costs. - [Install](https://agent.meffecta.com/install): the nine steps from an empty GCP project to a first run, with a check after each; what lives in the content repo; what is effective on push vs. on deploy. - [Jobs](https://agent.meffecta.com/jobs): the four triggers, every frontmatter field, webhook auth schemes, payload filter syntax, working in another repo, what a run is given, memory. - [Systems](https://agent.meffecta.com/systems): all 28 built-in skills with their exact variable names, the register format, and full walk-throughs for Google Ads, GA4/Search Console, Google OAuth, Microsoft 365, Slack, AgentMail/Svix, BigQuery and MongoDB. - [Operate](https://agent.meffecta.com/operate): the CLI commands, the silent failure modes `doctor` catches, the service's endpoints, moving between engine versions, running on your own machine. ## Prerequisites `gcloud` (authenticated), a linkable Google billing account, Node 24+, `git`, `gh`, a GitHub account, and a Claude credential (a Console API key or a Pro/Max subscription — step 4). Nothing else is installed — every command is `npx`. ## The install, in order Run every command after step 3 from inside the content repo. 1. `npx @meffecta/agent create-project` — note the project ID it prints; IDs are globally unique and often suffixed. 2. `npx @meffecta/agent link-billing --project ` — must report billingEnabled: true. 3. `gh repo create --private --clone` && `cd ` && `npx @meffecta/agent init` — scaffolds deployment.env, SYSTEM.md, systems/example.md, jobs/example.md. Fill in deployment.env (PROJECT is given; SERVICE and ARTIFACT_REPO are names you invent; REGION), write SYSTEM.md, then commit and push. 4. Mint credentials: a Claude credential — ANTHROPIC_API_KEY from platform.claude.com (billed per token; what Anthropic directs a product or service to use) OR CLAUDE_CODE_OAUTH_TOKEN from `npx @anthropic-ai/claude-code setup-token` (billed to a personal Pro/Max subscription; for your own individual use). Set one; the API key wins if both are set. Plus a fine-grained GitHub PAT for GITHUB_TOKEN (contents: read on the content repo; contents+PR write on any repo whose jobs open pull requests). 5. `npx @meffecta/agent setup-infra` — idempotent; `--dry-run` first prints everything it would create. 6. `npx @meffecta/agent set-secret AGENT_API_SECRET --random`, then `set-secret ANTHROPIC_API_KEY` (or `CLAUDE_CODE_OAUTH_TOKEN`), `set-secret GITHUB_TOKEN`, then `set-env GIT_REPO_URL=https://github.com//.git` — the service will not boot without GIT_REPO_URL. 7. `npx @meffecta/agent deploy` — rolls out the engine and creates the Cloud Scheduler triggers. 8. `npx @meffecta/agent status` / `jobs` / `doctor` / `verify-credentials` — seeing the jobs listed is what proves the service could read the content repo. 9. `npx @meffecta/agent run example` — the scaffolded job reaches nothing real and reports what it can see. ## Job triggers - `cron: "0 7 * * 1-5"` — a schedule, quoted, in the deployment's TIMEZONE (UTC if unset). - `webhook: ` — reachable at POST /webhooks/. Body is written to WEBHOOK_PAYLOAD.json. Acknowledged immediately; the run is carried out separately. - `inbox: gmail:
` or `inbox: agentmail:
` — the system prefix is REQUIRED, a bare address is refused. Requires `allowFrom:`, which is enforced in code and fails closed: a job without one refuses every message. Messages arrive as INBOX_TRIGGER.json. Which mailbox a `gmail:` job reads comes from AGENT_INBOX_MAILBOX (Workspace, domain-wide delegation, Cloud Run only, wins when set) or AGENT_INBOX_ACCOUNT (which GMAIL__* account; unset = the default GMAIL_* one); AGENT_INBOX_POLL_SECONDS sets the cadence and `sync-triggers` must be re-run after changing it. - `requireAuth: dmarc` or `dmarc, dkim` on an inbox job — what inbound mail must prove before it may trigger it. allowFrom: matches the From: HEADER, which anyone can forge; DMARC is the check that binds an authenticated domain to that header. `spf` is deliberately not an accepted value: SPF authenticates the envelope sender, not the From: header, so it passes for a spoofer sending from a domain they own. Read from the receiving MTA's own Authentication-Results (topmost header, authserv-id mx.google.com) — any other is treated as no verdict and refused. The verdict is recorded on every message in INBOX_TRIGGER.json and the audit trail whether or not it is enforced, so enforcement can be switched on from evidence; a refusal drops the message rather than queueing it. Unset enforces nothing. An unknown value, or the field on an `agentmail:` inbox (AgentMail authenticates on its own side and exposes no verdict), makes the job refuse all mail — never a silent downgrade. - a spawn — any run can schedule a follow-up run of its own job (fresh clone, fresh time limit, same identity and memory). Bounded at depth 3, 5 pending per job, 7 days ahead, 3600s. It buys time, never permission. Also: `npx @meffecta/agent run ` (optionally `--in `), `ask "…"`, and GET /ask in a browser. cron, webhook, inbox and allowFrom are registrations read when the service boots, so changing one needs `deploy`. Everything else in a job file is re-read every run, so it is live on push. ## Job frontmatter fields cron, webhook, inbox, allowFrom, requireAuth, auth, method, filter, repo, context, model, effort, systems, allowedTools, timeoutSeconds, disabled. All optional. `disabled: true` is the push-effective kill switch and stops pending spawns too. `systems:` decides which credentials are in the run's environment: comma-separated names of `systems/.md` entries, or of a `group:` those entries declare, and only their `requires` + `selectors` variables are passed. `*` means everything, and so does leaving the field out — an omission cannot fail closed, because a job stripped by accident would run, report every system unavailable, and look healthy. The engine's own variables always pass (so a scoped job can still clone); AGENT_API_SECRET never does, at any scope. A name matching no entry and no group grants nothing and is a doctor failure. A group sharing a name with a systems/ file fails the boot. A spawned follow-up inherits the same scope. Webhook `auth:` schemes: bearer (default), basic, github / hmac-sha256, slack, svix, svix:, none. slack and svix verify against the SENDER's secret (SLACK_SIGNING_SECRET, SVIX_SIGNING_SECRET / SVIX__SIGNING_SECRET), not AGENT_API_SECRET, and both enforce a five-minute timestamp window. Filter operators: `==`, `!=`, `~=` (contains), combined with ` && ` and ` || ` (spaces required). The left side is a dot path into the JSON body. ## Systems, and the variables each reads A system needs three things: a credential, `set-secret`/`set-env` on the deployment, and a file in `systems/` in the content repo. **Without the register entry the agent reports the system as unavailable even though the credential is valid** — the skills are shared across deployments and know no deployment's variable names. Register entry frontmatter: `system`, `skill`, `requires`, `selectors` (optional), `group` (optional, comma-separated labels a job's `systems:` can name instead of listing entries), `access`, `probe`. `requires` and `selectors` are load-bearing rather than descriptive: they are exactly what a job scoped to that entry is given. `npx @meffecta/agent create-system ""` has the deployment write one — it runs there, so it names the variables that are actually set, and it refuses an entry whose group:/filename would collide and stop the boot. `npx @meffecta/agent check-systems` re-validates every entry and cross-checks each job's `systems:` against them — no deployment or gcloud needed, it reads the files in front of you. Naming: `query-*` reads only, a bare system name reads and writes, `manage-*` writes. - send-email — GMAIL_CLIENT_ID, GMAIL_CLIENT_SECRET, GMAIL_REFRESH_TOKEN, GMAIL_ADDRESS - query-gmail — the same set; named accounts are GMAIL__REFRESH_TOKEN / _ADDRESS - google-calendar, google-drive — the same OAuth set, or Workspace domain-wide delegation - query-outlook — MSGRAPH_TENANT_ID, MSGRAPH_CLIENT_ID, MSGRAPH_CLIENT_SECRET, MSGRAPH__REFRESH_TOKEN (no default account) - agentmail — AGENTMAIL_API_KEY - query-google-marketing — no key (runs as the service account); GOOGLE_ADS_DEVELOPER_TOKEN, GOOGLE_ADS_MANAGER_CUSTOMER_ID, and per world _GA4_PROPERTY_ID, _SEARCH_CONSOLE_PROPERTY, _GOOGLE_ADS_CUSTOMER_ID - manage-google-ads — the same, with the Ads invitation raised above read-only. The only skill that spends money by writing; dry-run, capped and audited in code - query-posthog — POSTHOG_HOST, POSTHOG_API_KEY, _POSTHOG_PROJECT_ID - query-serp — SERPER_API_KEY - query-ahrefs — AHREFS_API_KEY - seo-geo-aeo — nothing (a procedure) - query-grafana — GRAFANA_LOGS_HOST, GRAFANA_LOGS_USERNAME, GRAFANA_API_KEY - query-coralogix — CORALOGIX_DOMAIN, CORALOGIX_API_KEY (the domain is region-specific) - query-elasticsearch — ELASTIC_URL, ELASTIC_API_KEY - query-bigquery — no key; grant the runtime service account roles/bigquery.dataViewer + jobUser on the queried project. Capped at 20 GiB scanned - query-mongodb — a connection string per database, named in the register (no shared variable) - query-hubspot / manage-hubspot — HUBSPOT_TOKEN (a private-app token; write scopes for the manage half) - query-kleer — KLEER_API_TOKEN - slack — SLACK_BOT_TOKEN, plus SLACK_SIGNING_SECRET for the inbound webhook half - linear — LINEAR_API_KEY (sent raw, with no `Bearer`) - webflow — WEBFLOW_TOKEN (cms:read / cms:write / sites:write, scoped per site) - mevisio — MEVISIO_HOST, MEVISIO_EMAIL, MEVISIO_PASSWORD (multi-tenant by hostname) - cloudflare — CLOUDFLARE_API_KEY (an API token, not the Global API Key), CLOUDFLARE_ACCOUNT_ID - browser — nothing of its own; per-site credentials come from the register - iso-27001 — nothing (a procedure) - spawn-job — nothing `npx @meffecta/agent connect` prints ordered walk-throughs for gmail, outlook, ga4, grafana, posthog, serper, hubspot, kleer, mongodb, cloudflare and inbox. ## Google Ads takes longest — start it early 1. A manager account (MCC) with the ad account linked under it. Both ids are digits only, no dashes. 2. A developer token from the MANAGER account's API Center. It is issued with test-account access only; apply for Basic to use it against a real account. Google's review is normally days. 3. Invite the runtime service account (the `acts as` address from `npx @meffecta/agent status`) into the Ads account: Admin → Access and security. Read only for reporting; Standard only if jobs should change the account. There is no key to mint — the token comes from the Cloud Run metadata server, so this works on Cloud Run and nowhere else. 4. `set-secret GOOGLE_ADS_DEVELOPER_TOKEN`, `set-env GOOGLE_ADS_MANAGER_CUSTOMER_ID=…`, `set-env _GOOGLE_ADS_CUSTOMER_ID=…`. The manager id is shared and never takes a world prefix. 5. Write the register entry naming query-google-marketing (read) and/or manage-google-ads (write). ## Things that are true and easy to get wrong - The engine is deployed by you and pulls no updates on its own. `npx @meffecta/agent@latest deploy` is how a deployment moves version; `deploy --tag ` rolls back. - A deploy restarts the service, which is what registers new triggers, and re-syncs the schedules. - Runs of one job are serial; different jobs run in parallel (MAX_CONCURRENT_RUNS, default 3). - Runs are at-least-once: an instruction should be safe to repeat. - Every run's working directory is a fresh clone of the content repo, whatever code it touches. `repo:` clones the repo a job works ON beside it and names it as $AGENT_WORK_REPO; it is not the working directory, contributes no skills, and its CLAUDE.md is material rather than instructions. The content repo's SYSTEM.md plus the engine's baseline are what bind every run, so a product repo cannot change what the agent may do by editing a file in itself. A CLAUDE.md in the CONTENT repo is removed from the clone before the run — it would precede everything and be layered with nothing; SYSTEM.md is the channel that is composed and reviewable. - systems/ and worlds/ are never copied into a working repo; the run is given their paths. Only skills are materialised into the working directory. - Skills resolve in three layers: the working repo's, then the content repo's, then the engine's. Matched by directory name. - Every prompt, result and transcript goes to a write-once audit bucket in your own project, kept a year. - Runs are billed to your own Claude subscription. Meffecta charges nothing and holds no credential of yours.