Docs / Teams

Shared executor

On a laptop, the MCP server runs the engine in-process with your own credentials. For anything shared, run an executor next to the systems instead. Teams want this for three reasons:

  • Agents never hold credentials. The executor has the DSNs and kubeconfigs. Agents, SDKs and CI gates send it proposals.
  • Policy is enforced where it can't be bypassed. A developer can't edit the policy file on the executor or approve their own change.
  • One audit log for the whole team, exported to OpenTelemetry if you like.

Run it

changeward executor token alice --roles propose     # prints the token once, and the config entry to add
changeward executor token sre-oncall --roles approve
changeward executor serve --addr 0.0.0.0:8443 --tls-cert cert.pem --tls-key key.pem

changeward.yaml on the executor:

targets:
  app:  { adapter: postgres, env: prod, dsn: "${APP_DATABASE_URL}" }
  cluster: { adapter: kubernetes, env: prod, options: { namespace: shop } }
executor:
  tokens:
    - { name: alice,      sha256: "…", roles: [propose] }
    - { name: sre-oncall, sha256: "…", roles: [approve] }
    - { name: argocd-gate, sha256: "…", roles: [propose] }

Only the SHA-256 of each token is stored. A token's name is the delegating human or service, recorded as user:<name>. Roles:

Role Can
propose Start sessions, propose, apply what policy allows, undo, query, checkpoint, confirm inline approvals on its own changes, run the GitOps gate and gateway authz
approve Read changes and approve or reject out-of-band, but never its own changes (separation of duties)
admin Everything

Clients

# MCP server for a coding agent, backed by the executor:
changeward mcp postgres --target app --executor https://changeward.internal:8443   # token in $CHANGEWARD_EXECUTOR_TOKEN

# Approvals:
CHANGEWARD_EXECUTOR_URL=https://changeward.internal:8443 CHANGEWARD_EXECUTOR_TOKEN=$SRE_TOKEN changeward approve c_…

# GitOps gate:
changeward k8s gate --manifests rendered/ --target cluster --executor https://changeward.internal:8443

Python and TypeScript SDKs call the same API.

Helm

helm install changeward deploy/helm/changeward-executor -n changeward --create-namespace -f values.yaml
image: { repository: ghcr.io/srawlin/changeward, tag: latest }
existingSecret: changeward-env           # env vars referenced as ${VAR} in config (DSNs, Slack tokens)
persistence: { enabled: true, size: 1Gi } # SQLite audit log; one replica
rbac:
  kubernetes:                            # for kubernetes targets, using the in-cluster service account
    enabled: true
    namespaces: [shop, payments]         # or clusterWide: true
config: |
  targets:
    app: { adapter: postgres, env: prod, dsn: "${APP_DATABASE_URL}" }
    cluster: { adapter: kubernetes, env: prod }
  executor:
    tokens: [ … ]
policy: |                                # optional organization policy
  version: 1
  rules:
    - name: prod-k8s-needs-sre
      when: change.lane == "ci" && change.env == "prod" && change.adapter == "kubernetes"
      decide: require
      channel: out-of-band
      approvers: ["group:sre"]

The pod runs as non-root with a read-only root filesystem. Liveness and readiness use /healthz. The chart was deployed in kind and used by the Python SDK and the Argo CD PreSync gate.

systemd

deploy/systemd/changeward-executor.service runs it as a changeward user, with /etc/changeward/changeward.yaml and secrets in /etc/changeward/env.

API

All routes need Authorization: Bearer <token>. Gateways may send X-Changeward-Token instead and keep the client's own Authorization header.

Route Role
POST /v1/sessions, GET /v1/sessions/{id}, POST /v1/sessions/{id}/end propose
GET /v1/sessions/{id}/changes, POST /v1/sessions/{id}/checkpoint, POST /v1/sessions/{id}/query propose
POST /v1/changes (propose), POST /v1/changes/{ref}/apply, POST /v1/changes/{ref}/undo propose
POST /v1/changes/{ref}/approve-inline propose (only where policy allows inline)
GET /v1/changes/{ref} propose or approve
POST /v1/changes/{ref}/approve approve
POST /v1/gitops/gate propose
/v1/authz/mcp, POST /v1/authz/tool any valid token (gateways.md)
GET /healthz none

Limitation: the store is SQLite, so run a single replica. A Postgres-backed store for high availability is planned.

Edit this page on GitHub · synced from 89c04c9