# AGENTS — Developer Helper MCP **Mission:** Get a coding agent + human to a **first green** Zeus Client app turn. ## Load order 1. This file 2. [docs/TOOLS.md](docs/TOOLS.md) — Helper tool catalog (when / args / side effects / do-not) 3. [Zeus Client docs](https://docs.koten.ai/zeus-client) — **live** published human docs hub 4. [For AI agents](https://docs.koten.ai/zeus-client/for-ai-agents) · [Using Zeus Client](https://docs.koten.ai/zeus-client/using-zeus-client) · [Start](https://docs.koten.ai/zeus-client/start) · [Errors](https://docs.koten.ai/zeus-client/errors) · [Dev Helper MCP](https://docs.koten.ai/zeus-client/dev-helper-mcp) 5. Live Zeus public API **:8080** (readiness / stamp / smoke — never Hub **:9091** from the app path) 6. [agent-index.yaml](https://github.com/koten-ai/koten_docs/blob/zeus-v1.0.0/agent-index.yaml) — machine map only (do **not** clone `koten_docs` or grep the Zeus engine for first green) 7. [zeus_chat_request](https://github.com/koten-ai/zeus_chat_request) for templates ## Hard constraints - Public API **:8080** only (never Hub **:9091** from app path) - Never invent `contract_hash` - Catalogs: live stamp preferred; `fetch_chat_request` is **template only** - Checklist **4.2** copies `bootstrap` `chat_request.contract` into the bound app `config.json` `zeus.scope_contracts`. `explain_scope` parses `bootstrap.chat_request` when `GET /v1/ai/chat_request.json` has no `## MINI-SCHEMA` heading. `bind_contract` with no path returns that snippet. A helper `chat_request.json` whose only field is `chat_request_dir` is a directory pointer, not an unstamped catalog. The Hub / Workbench note applies only when that live document has no real `contract.hash`. Pinning 4.2 does not turn Pour on. A done 4.1 row stays done when `data/chat_requests` is later absent. - No secrets in tool results, checklist evidence, the write-once sample creation logs (`beer-sample-build.md`, `travel-sample-build.md`, `yelp-demo-build.md`), the support journals (`beer-sample-support.md`, `travel-sample-support.md`, `yelp-demo-support.md`), or the wish files (`beer-sample-wish.md`, `travel-sample-wish.md`, `yelp-demo-wish.md`). Passwords, bearer tokens, and API keys stay out of wish `what` and `why`. - `verify_local_setup` imports `zeus_client` in this Helper process. The app `.venv` does not satisfy that check. Launch with `uvx --from 'zeus-dev-helper-mcp[agent]' zeus-dev-helper-mcp` (or `pip install 'zeus-dev-helper-mcp[agent]'` in that interpreter) and restart the MCP server. A Codex `:workspace` profile with network off reports pip `[Errno -2] Name or service not known` (`EAI_NONAME`). The app `requirements.txt` install stays a separate step from a process that has network. ## Tools to use first Default surface (`core`): `doctor` → (if user named URL/sample) `set_prereq` → `start_project` → `next_step` → `readiness_check` → `use_sample` | `scaffold_app` → `bind_contract` → `recommend_surface` → `smoke_test_zeus` / `smoke_test_agent` → `diagnose_error`. `start_project(sample=beer|travel|demo_yelp|api)` marks checklist 0.1 done. `next_step` then recommends `set_prereq` when no Zeus URL is stored, otherwise `readiness_check` then `use_sample` or `scaffold_app`, and only tools in the enabled toolset (`explain` and `recommend_motion` stay off core). `recommend_surface` for a website names each input. List, chips, typeahead, paging, and detail are Direct find, get, and search and need no LLM key. The question box is One chat: `run_turn` with `chat_request` omitted, `max_rounds` 4 on a catalog page and up to 8 on a copilot. `needs_llm=false` leaves that box off. A job hint is two or more Zeus URLs, two or more scopes, or a report wait of 20 seconds to 5 minutes, and only after the operator confirms. Smoke-green does not write `jobs.host_url`. **Default app kind is UI** (`start_project(sample=travel)` → `use_sample` follows the beer creation procedure for public `demo_travel_sample` at https://github.com/koten-ai/demo_travel_sample.git. When the user names a version for beer-sample, travel-sample, or yelp-demo (aliases `demo_yelp`, `yelp-demo`, `demo-yelp`, `yelp_demo`, `yelpdemo`), pass `use_sample(version=...)`. A named tag that exists is cloned exactly, including an older tag when a newer one also exists. An omitted version or a missing tag uses the latest semver tag (major, minor, and patch compared as integers; tag names `0.1.0` and `v0.1.0` are accepted and the remote tag name is passed to `git clone --branch`). `main` is used only when that repository has no semver tags. `demo_beer_sample`, `demo_travel_sample`, and `demo_yelp` are tagged `0.1.0`. `git ls-remote` failure, missing git, or a clone timeout is `clone_failed` and does not fall through to another ref. An existing checkout stays in place. The destination is `sample_dir`, or `parent_dir` plus `project_name`. A blank parent is the directory next to this Helper checkout. A foreign default directory moves to the next open sibling. An explicit foreign `sample_dir` returns `foreign_sample_dir` and writes nothing. A recorded checkout that answers `GET /api/health` stays in place (`already_serving`, no `host_question`). Basic auth with no process login returns `login_not_in_process` before git clone. The create path does not rewrite Docker files. `python -m travel_planner` listens on port 5000 unless `PORT` is set. `use_sample` writes mode 600 `.env` from the non-secret Zeus and LLM names, keeps populated values, and fills empties. A rewrite of an existing `.env` sets `env_change`. `settings_written` names which of `ZEUS_AUTH_MODE`, `ZEUS_USERNAME`, `LLM_BASE_URL`, and `LLM_MODEL` changed. `settings_kept` gives the populated values that stayed. `ZEUS_PASSWORD`, `ZEUS_BEARER_TOKEN`, and `LLM_API_KEY` stay presence-only in `secret_presence`. Restart the app before `run_turn` when `env_change` is true. `ZEUS_USERNAME` defaults to `demo_1`. Checklist **1.2** stays open until this process has an LLM key, because this page is search and has no Direct list. Checklist **3.2** stays open until `verify_local_setup` sees `LLM_API_KEY`, `XAI_API_KEY`, or `OPENAI_API_KEY` in the app `.env`. `set_prereq(has_llm_key=true)` does not count. After the app is on disk, checklist **5.1**, **5.2**, and **6.1** recommend `website_green`. Travel is one scope: do not add `handoff_to_multi` after smoke-green and do not write `jobs.host_url`). **Beer / website:** `start_project(sample=beer)` → `use_sample(sample=beer)` shallow-clones public `demo_beer_sample` (https://github.com/koten-ai/demo_beer_sample) when missing. A named tag that exists is cloned exactly; an omitted version or a missing tag uses the latest semver tag; `main` is used only when that repository has no semver tags. The repository is tagged `0.1.0`. It sets `DEMO_BEER_SAMPLE_DIR`, and, when that checkout has both `Dockerfile` and a compose file, the result includes `host_question`. Follow `host_question` and `next_action` before starting. Otherwise it starts that checkout with `python main.py` after a Python 3.11 or newer virtualenv and `pip install -r requirements.txt`. `use_sample` writes mode 600 `.env` from `.env.example`, keeps populated values, and adds missing template keys. A rewrite of an existing `.env` sets `env_change`. `settings_written` names which of `ZEUS_AUTH_MODE`, `ZEUS_USERNAME`, `LLM_BASE_URL`, and `LLM_MODEL` changed. `settings_kept` gives the populated values that stayed. `ZEUS_PASSWORD`, `ZEUS_BEARER_TOKEN`, and `LLM_API_KEY` stay presence-only in `secret_presence`. Restart the app before `run_turn` when `env_change` is true. `LLM_API_KEY`, `LLM_BASE_URL`, and `LLM_MODEL` are resolved together. An `OPENAI_API_KEY` or `XAI_API_KEY` is copied only when the base URL and model match that provider. The clone keeps `LLM_MODEL=grok-4.5-latest`. `run` and `next_action` do not say to copy `.env.example` over that file. `python3 -m venv` is used only when `python3` is 3.11 or newer. `website_green` does not run pip on an older `.venv`. Lists are Direct find and get. Pour is One chat: `rt.agent.run_turn` with `chat_request` omitted so `catalog.load_for_turn` merges SCOPE BRIEF + MINI-SCHEMA into the model request. `max_rounds` on that catalog page is 4. A Pour result shows the fields the question asked for, including fields from a joined entity, while the current tab stays selected. The Beers and Breweries controls stay the Direct lists. Search sends the question text and does not add an entity type. The published tree includes `Dockerfile`, `docker-compose.yml`, and `.dockerignore`. Compose bind-mounts `./data` and does not copy `.env` or `data/` into the image. Startup `catalog.sync()` writes `data/chat_requests/beer-sample__default/chat_request_analytics_v2.json` from the live pin. A missing catalog is HTTP 502. `run_turn` does not swallow `CATALOG_NOT_FOUND`. Lists render with no LLM key, so checklist **1.2** and `validate_env` do not block them. Pour stays off until `LLM_API_KEY`, `XAI_API_KEY`, or `OPENAI_API_KEY` is in the app `.env` (checklist **3.2**, `verify_local_setup`) and in the Helper process for `smoke_test_agent`. That key is still required for Pour when `has_llm_key=false`. `config.json` `llm.api_key_env` stays that variable name. `set_prereq(has_llm_key=true)` does not count, and the tools never accept the secret. The BFF does not build a pipeline body. Do not clone `demo_travel_sample` on the beer path. `rt.data.verb('pipeline')` stays rejected (060010). **Yelp demo:** `start_project(sample=demo_yelp)` → `use_sample(sample=demo_yelp)` clones the LocalAI template [demo_yelp](https://github.com/koten-ai/demo_yelp) when missing (aliases `yelp-demo` / `demo_yelp`). A named tag that exists is cloned exactly; an omitted version or a missing tag uses the latest semver tag; `main` is used only when that repository has no semver tags. The repository is tagged `0.1.0`. and sets `DEMO_YELP_SAMPLE_DIR`. When `parent_dir` is set, the clone is created in that directory. Docker is the preferred yelp-demo elicitation option and is guide-only: `DOCKER_BUILDKIT=1 docker compose up -d --build`. UI http://localhost:3000. API http://localhost:5000. The helper does not run that command. The other option is one choice for both host processes: `pip install -e . && python -m local_guide` on port 5000 and `cd frontend && npm install && npm run dev` on port 5173 (the SPA proxies /api to port 5000). `website_green` follows the accepted value. Docker leaves the host processes unstarted and repeats the Compose command. Python keeps the host start and the `GET /api/health` check. That choice does not use `has_serve_choice`, which looks for a file named `Dockerfile`. `use_sample` writes a gitignored mode-600 `config.json` from this process (Zeus URL, bucket, scope, username, and password). `llm.api_key_env` stays `LLM_API_KEY`. The first yelp-demo create, while `yelp-demo-build.md` is absent and the host shows a form, asks a Couchbase card after the Zeus connection card, then a server card, then the LLM card. Both cards are yelp-only and use the same first-create gate. The server field is `docker` or `python`. `docker` is the default and the first enum value. A declined or cancelled card does not create the app. `couchbase_query_url` and `couchbase_username` are strings. `COUCHBASE_PASSWORD` stays in the app `.env` and is not collected on the form or as a tool argument. An accepted card writes the two strings into a mode-600 `.env` (created from `.env.example` when missing) and leaves an existing `COUCHBASE_PASSWORD` line unchanged. Beer, travel, the API scaffold, and bare `sample=yelp` do not ask the Couchbase card or the server field. When the card is not shown, `ask_user` adds text rows for `COUCHBASE_QUERY_URL` and `COUCHBASE_USERNAME` and tells the user to write `COUCHBASE_PASSWORD` in the app `.env`. A 401 on the session mint stays red. The header badge reads `GET /api/health`. `use_sample` also downloads the public business photo set into `frontend/public/business-images//{1,2,3}.png` (no `biz:` prefix, no API key). Search POSTs `/api/search` and sends the live chat request for `yelp-demo` / `_default` as the session body. Search cards unwrap a hybrid `{node, score}` hit and a nested pipeline row. `use_sample` fills latitude and longitude from the source document `biz:yelp:` when the project step omits them, including a cached business page. The business page GETs `/api/business/{id}` when the id is not in the bundled catalog. Review text is the source document, not a `rev:` doc key. Do not scaffold a replacement and do not copy `demo_beer_sample`. Do not clone `demo_travel_sample` on that path. Bare `sample=yelp` stays the multi-agent handoff. **API-only** when the user asks for API/REST: `start_project(sample=api)` → `scaffold_app(app_kind=api, coding_language=python)` (FastAPI). Credentials → env + `set_prereq` presence flags only. When auth is basic and a name is missing, write `ZEUS_USERNAME` and `ZEUS_PASSWORD` in a mode-600 env file and call `load_process_login` with that path. The password is not a tool argument. When a result includes `ask_user`, follow that result's `next_action`. A choice row has `question` and `options`. A text or password row uses the host tool's text field. Skip a row whose `when` does not match. `llm_options` lists Grok (`https://api.x.ai/v1`, `grok-4.5-latest`), OpenAI (`https://api.openai.com/v1`, `gpt-5.6-sol`), Anthropic (`https://api.anthropic.com/v1`, `claude-opus-5-5`), Gemini (`https://generativelanguage.googleapis.com/v1beta/openai/`, `gemini-3.8-flash`), and Other. Set `LLM_BASE_URL` from the chosen base URL, ask `LLM_MODEL` with that option's model as the default, then ask `LLM_API_KEY`. `load_process_login` also reads `ZEUS_AUTH_MODE`, `LLM_BASE_URL`, and `LLM_MODEL`. Do not pass the API key as a tool argument. `use_sample` for beer, travel, and demo_yelp, and `scaffold_app` for the API sample, show this full set on every build, including a second build when the env file already has values. Order: `ZEUS_AUTH_MODE` (`none`, `basic`, `bearer`); basic asks `ZEUS_USERNAME` as text (beer default `tester_beer`, travel default `demo_1`) and `ZEUS_PASSWORD` as a password; bearer asks `ZEUS_BEARER_TOKEN` as a password; none skips those; then the LLM provider. Other asks for the provider name and `LLM_BASE_URL`. A named provider sets `LLM_BASE_URL` from that entry. `LLM_API_KEY` is a password field. Prefill text fields from the current value. Write the same names in the app `.env`. `config.json` `llm.api_key_env` stays `LLM_API_KEY`. `set_prereq` stores `auth_mode` and presence flags only. The password, bearer token, and API key stay out of tool results, checklist evidence, creation logs, and support journals. `validate_env`, `next_step`, `readiness_check`, `smoke_test_agent`, `verify_local_setup`, and a `load_process_login` that is still missing a name keep the shorter missing-name questions. A red `readiness_check` is `ok: false`, not a tool error. `doctor(detail=env|compat|cache)` covers checks that also live on the lint toolset. Wiring Zeus into an arbitrary existing repo is **out of scope**. Business rules: when the user asks to add or inject a business rule, call `inject_business_rules`. An omitted app uses the current directory when that directory is a Zeus app, otherwise the checklist sample (the beer app just created). `ask_user` means ask and wait, then call again with `app_dir` or `sample`. An explicit path that is not a Zeus app returns `not_a_zeus_app` and the message `app is not a Zeus enabled app`. The tool writes `config.json` `settings.rules` only. `wire_required` means the app builds `ClientSettings` itself, so pass `settings.rules` into that constructor. Python files stay unchanged. Knowledge is resources: `zeus-helper://checklist`, `zeus-helper://glossary/{topic}`, `zeus-helper://verbs/{name}`, `zeus-helper://policy/hash-boundary`, `zeus-helper://policy/req-id`, `zeus-helper://catalog/modes`. Prompts: `first_green`, `smoke_question`, `support_pack`. `scaffold_app` / `smoke_test_agent` emit **ZeusRuntime** + `rt.agent.run_turn` (not V1 `ZeusClient` / `run_agent`). Semantic cache stays **off**. Opt-in toolsets (`ZEUS_DEV_HELPER_TOOLSETS=core,lint,catalog` or `all`): catalog / lint / travel / support / handoff. Travel sample: `use_sample` (core) / `travel_golden_path` (travel toolset). The clone is https://github.com/koten-ai/demo_travel_sample.git. A named tag that exists is cloned exactly; an omitted version or a missing tag uses the latest semver tag; `main` is used only when that repository has no semver tags. The repository is tagged `0.1.0`. Start with `python -m travel_planner` on port 5000 unless `PORT` is set. The create path does not rewrite Docker files. After 5.1+5.2 green: `recommend_data_plane_mcp`, `emit_mcp_config`, `handoff_to_multi` — **handoffs only** (handoff toolset). A one-scope sample, including beer and travel, does not take `handoff_to_multi` as the next build step. `handoff_to_multi` writes `jobs.host_url` only after the operator confirms two or more URLs, two or more scopes, or a report wait of 20 seconds to 5 minutes. Design freeze: `docs/DESIGN.md`. 0.7 increment: `docs/DESIGN-0.7.md`. Tool catalog: `docs/TOOLS.md`. ## Not this MCP Data-plane tools, Hub admin mutations, multi-agent jobs (ZJA). Integrating Zeus into an arbitrary existing application (use `demo_travel_sample` / `use_sample(sample=beer)` / `use_sample(sample=demo_yelp)` / `use_sample` instead).