# Conway — agent contract (v0.1)

Conway is an agent-native cloud platform: you bring source code, Conway gives
you a live URL. Everything is driven by this API — no humans in the loop.

- REST base: the gateway you fetched this file from. All paths below are
  relative to it. Interactive docs: `/v1/docs`; machine spec: `/v1/openapi.yaml`.
- Auth: every `/v1/` request needs `Authorization: Bearer <token>`.
- CLI: the `conway` binary wraps this API one-to-one. Install it with
  `curl -fsSL <endpoint>/install.sh | sh` (served by the gateway; macOS/Linux,
  amd64/arm64). When stdout is not a TTY it emits JSON automatically; `--json` forces it.
- Errors are always `{code, message, hint, retryable}`. Read the `hint` —
  it tells you the next action. Codes are enumerated at the bottom.

## Quickstart

Run these commands from your application's source directory. They are the
complete path from zero to a live URL. Set `CONWAY_ENDPOINT` and
`CONWAY_TOKEN` first.

```quickstart
conway init $PROJECT
conway deploy --service app --dir $SRC_DIR
conway status
```

`conway init` is idempotent: re-running returns the existing project with
`"created": false`. `conway deploy` uploads the directory (gzipped tarball,
512MB max), builds it in an isolated job, and streams build logs; on success
it prints the live URL (`url` key in JSON output). `conway status` returns the
full project state tree: services, releases, env keys, recent events.

A `Dockerfile` at the source root is used when present. The app MUST listen
on the `PORT` env var (default 8080) — the health gate probes it, and a
release that never listens is rolled back automatically.

## Verbs (CLI ⇄ REST)

| CLI | REST | operationId |
|---|---|---|
| `conway init <name>` | `POST /v1/projects` | `createProject` |
| `conway status` | `GET /v1/projects/{project}` | `getProject` |
| `conway delete <name> --confirm` | `DELETE /v1/projects/{project}?confirm=true` | `deleteProject` |
| `conway deploy [--service s] [--dir d]` | `POST /v1/projects/{project}/services/{service}/deploys` | `createDeploy` |
| `conway services list` | `GET /v1/projects/{project}/services` | `listServices` |
| `conway services scale <s> --replicas N` | `PATCH /v1/projects/{project}/services/{service}` | `scaleService` |
| `conway rollback [--service s] [--to release]` | `POST /v1/projects/{project}/services/{service}/rollback` | `rollbackService` |
| `conway restart <s>` | `POST /v1/projects/{project}/services/{service}/restart` | `restartService` |
| `conway env set K=V...` | `PUT /v1/projects/{project}/env` | `setEnv` |
| `conway env list` | `GET /v1/projects/{project}/env` | `listEnv` |
| `conway logs [--service s] [-f]` | `GET /v1/projects/{project}/services/{service}/logs` (SSE) | `streamLogs` |
| `conway projects list` | `GET /v1/projects` | `listProjects` |
| `conway add postgres\|redis\|mongo [--name n]` | `POST /v1/projects/{project}/datastores` | `createDatastore` |
| `conway db info <n>` | `GET /v1/projects/{project}/datastores/{name}` | `getDatastore` |
| `conway db delete <n> --confirm` | `DELETE /v1/projects/{project}/datastores/{name}?confirm=true` | `deleteDatastore` |
| `conway db backups <n>` | `GET /v1/projects/{project}/datastores/{name}/backups` | `listBackups` |
| `conway db backup <n>` | `POST /v1/projects/{project}/datastores/{name}/backups` | `createBackup` |
| `conway db restore <n> --backup <key>` | `POST /v1/projects/{project}/datastores/{name}/restore` | `restoreBackup` |
| (browse tables/keys) | `GET /v1/projects/{project}/datastores/{name}/tables` | `listDatastoreTables` |
| (run SQL / redis command) | `POST /v1/projects/{project}/datastores/{name}/query` | `queryDatastore` |
| `conway repos create <n> [--deploy-service s]` | `POST /v1/projects/{project}/repos` | `createRepo` |
| `conway repos list` | `GET /v1/projects/{project}/repos` | `listRepos` |
| `conway repos info <n>` | `GET /v1/projects/{project}/repos/{repo}` | `getRepo` |
| `conway repos delete <n>` | `DELETE /v1/projects/{project}/repos/{repo}` | `deleteRepo` |
| `conway repos refs <n>` | `GET /v1/projects/{project}/repos/{repo}/refs` | `listRepoRefs` |
| `conway repos log <n> [--ref r] [--limit N]` | `GET /v1/projects/{project}/repos/{repo}/commits` | `listRepoCommits` |
| (browse the file tree) | `GET /v1/projects/{project}/repos/{repo}/tree` | `getRepoTree` |
| (read a file) | `GET /v1/projects/{project}/repos/{repo}/file?path=` | `getRepoFile` |
| `conway repos commit <n> <file>... -m msg` | `POST /v1/projects/{project}/repos/{repo}/commits` | `commitRepoFiles` |
| `conway tokens issue --caps …` | `POST /v1/tokens` | `createToken` |
| `conway tokens list` | `GET /v1/tokens` | `listTokens` |
| `conway tokens revoke <id>` | `DELETE /v1/tokens/{id}` | `revokeToken` |
| `conway events -f` | `GET /v1/projects/{project}/events` (SSE) | `streamEvents` |
| `conway admin capacity` | `GET /v1/admin/capacity` | `clusterCapacity` |
| (poll a long op) | `GET /v1/operations/{id}` | `getOperation` |
| (stream a deploy) | `GET /v1/operations/{id}/stream` (SSE) | `streamOperation` |

## Long operations

`createDeploy` returns `202` immediately:

```json
{"operation_id": "…", "stream_url": "/v1/operations/…/stream", "service_url": "http://app-myproj.…"}
```

Either poll `GET /v1/operations/{id}` until `status` is `succeeded|failed`,
or consume the SSE stream (events: `log`, `phase`, `done`). The stream
replays from the start, so reconnecting after a disconnect is lossless. The
`done` event carries the same `error`/`result` payload as the operation row.

Deploy phases: `queued → building → pushing → rolling_out → healthy`
(failures end in `failed` or `rolled_back`).

## Releases and rollback

Releases are immutable and content-addressed. A failed health gate
automatically rolls back to the previous release and reports
`HEALTH_CHECK_FAILED` with the app's log tail. `conway rollback` re-points
the service at the previous (or a named) release in seconds — no rebuild.

## Environment variables

`conway env set KEY=value` merges into the project env and rolling-restarts
affected services; the response lists `restarted_services`. Values are never
returned by any read endpoint — `conway env list` shows names only. `PORT` is
always injected.

## Datastores

`conway add postgres` (or `redis`) provisions a single-instance engine inside
your project, generates credentials, and injects the connection URL into ALL
project services with a rolling restart — you never plumb credentials:

- postgres → `DATABASE_URL` (plus `PGHOST`/`PGPORT`/`PGUSER`/`PGPASSWORD`/`PGDATABASE`); TLS on, use `sslmode=require`
- redis → `REDIS_URL` (password auth)
- mongo → `MONGO_URL` (FerretDB: standard Mongo drivers work; keep `authMechanism=PLAIN` from the URL; advanced Mongo features like multi-doc transactions may differ — it is Mongo wire protocol over Postgres)
- custom `--name foo` → `FOO_URL`

Datastores are ClusterIP-only: reachable from your services, never from the
internet. Backups run nightly to an object store (7-day retention); trigger
one with `createBackup`, list them with `listBackups`, and `restoreBackup`
applies one back (postgres round-trips fully; redis/mongo are backup-only in
v1). You can inspect them through the API: `listDatastoreTables` browses
postgres tables (or redis keys with types), and `queryDatastore` runs SQL
(postgres) or a single command like `HGETALL user:1` (redis) — results are
tabular JSON capped at 500 rows, and every query is audited. `conway db info <name>` reports `provisioning|ready` — wait for
`ready` before first connect, or retry connects on startup (recommended).
Deploys after `add` see the env automatically; existing services are
restarted by `add` (response lists `restarted_services`).

## Tokens and delegation

`createToken` mints scoped bearer tokens. Non-admin issuers can only grant
capabilities they themselves hold, scoped to projects within their own scope
— so an agent can safely sub-delegate (e.g. a deploy-only token for a child
agent) without escalation. The secret is returned exactly once. Admin tokens
can list and revoke.

## Git repositories (Forge)

Conway hosts git per project. Two ways to put code in a repo, both authenticated
by your **Conway token** — you never hold a separate git credential:

1. **`git` over HTTPS.** `createRepo` returns a `clone_url`. Use the Conway token
   as the git password: `git push https://x:$CONWAY_TOKEN@git.agentcloud.ac/<project>/<repo>.git`.
   The gateway authorizes (needs the `manage_repos` capability + project scope)
   and proxies to the Forge, provisioning your git identity on first use.
2. **`commitRepoFiles` — no local git.** POST files (`{path, content}`) in one
   commit; an empty repo's first branch is created for you. This is the
   ergonomic path for an agent generating code (`conway_commit_files` in MCP).

Read back with `getRepoTree`, `getRepoFile`, `listRepoCommits`, `listRepoRefs`.
A repo created with `deploy_service` set auto-deploys that service on push
(push-to-deploy). Requires the `manage_repos` capability.

## MCP server

`conway-mcp` (single binary) exposes every verb above as MCP tools over
stdio — `conway_deploy` packs a local source directory itself, so an agent
with filesystem access deploys in one tool call and gets back the final URL
or the structured error. Configure with `CONWAY_ENDPOINT` + `CONWAY_TOKEN`:

```
claude mcp add conway -e CONWAY_ENDPOINT=https://api.agentcloud.ac -e CONWAY_TOKEN=… -- conway-mcp
```

## Error codes

| code | meaning | what to do |
|---|---|---|
| `NO_PROVIDER_DETECTED` | no Dockerfile / buildable provider found | add a Dockerfile at the source root |
| `BUILD_COMMAND_FAILED` | the build itself failed | read `log_tail`, fix source, redeploy |
| `IMAGE_PUSH_FAILED` | built but couldn't push to registry | retry the deploy (`retryable: true`) |
| `SOURCE_TOO_LARGE` | upload exceeds 512MB | exclude artifacts via `.conwayignore` |
| `TIMEOUT` | build exceeded 30 minutes | reduce build work, retry |
| `HEALTH_CHECK_FAILED` | app never listened on `$PORT` | bind `0.0.0.0:$PORT`; check `log_tail` |
| `UNAUTHENTICATED` | missing/invalid bearer token | send `Authorization: Bearer <token>` |
| `CAPABILITY_MISSING` | token lacks the named capability | request a token with that capability |
| `QUOTA_EXCEEDED` | project resource quota hit | scale something down or request more |
| `NOT_FOUND` | no such project/service/release | check `conway status --json` |
| `INVALID_INPUT` | request shape wrong | follow the `hint`; see `/v1/docs` |
| `CONFLICT` | state conflict (e.g. nothing to roll back to) | follow the `hint` |
| `INTERNAL_ERROR` | Conway-side fault | retry once; report if persistent |

## .conwayignore

`conway deploy` always excludes `.git`, `node_modules`, `.conway`,
`__pycache__`, `.venv`. Add more prefixes in a `.conwayignore` file (one per
line) at the source root.

## Coming in later versions

Datastores (`conway add postgres|redis|mongo`), custom domains
(`conway domains add`), raw TCP/dedicated IPs (`conway expose tcp`), DNS
(`conway dns`), MCP server. The version header of this file is
`X-Conway-Skill-Version`; pin with `/skill.md?v=1`.
