---
title: "Owner/admin update of a live Mnemom Agent session's goal contract"
method: POST
path: "/agent/sessions/{conversationId}/contract"
tags: ["Code"]
---

# Owner/admin update of a live Mnemom Agent session's goal contract

`POST /agent/sessions/{conversationId}/contract`

## Request

- Base URL: `https://api.mnemom.ai/v1`
- URL: `https://api.mnemom.ai/v1/agent/sessions/{conversationId}/contract`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-Mnemom-Api-Key`
  - API key in cookie `mnemom_session`

Writes a NEW contract version under the SAME goal id into the gateway's sealed-contract record for `conversationId` (`POST /internal/goal/contract`), returning the new version and both hashes. Applies from the conversation's next turn — the request already in flight was judged against the version it read at its own start — and the session is notified once on that next turn. Never infers an update from conversation content; the caller supplies the full new contract. Resolves the org and confirms `agent_id` ownership exactly like `GET /v1/agent/sessions/{conversationId}` (same 404 `agent_not_found` collapse for a missing or cross-org agent), but requires the owner/admin role (a write), not any member role. A caller's own SESSION credential (a gateway-only-scoped API key, or one minted with `kind: 'session'`) is refused with 403 `session_credential_forbidden` before the gateway is ever reached — a session must never update its own contract.

## Path parameters

- `conversationId` string, required

## Query parameters

- `agent_id` string, required
- `org_id` string

## Request body

- object
  - `contract` object, required: The full new goal contract (replaces the live one under the same goal id). Every ceiling (`max_turns`, `budget_usd`, `stall_turns`, `time_limit_s`) follows replace semantics: one omitted here is removed, not kept. Only `mode` carries over when omitted.
    - `goal_id` string
    - `statement` string, required
    - `requirements` string[], required
    - `allowed_paths` string[], required
    - `forbidden_paths` string[], required
    - `max_turns` number
    - `budget_usd` number
    - `stall_turns` number
    - `time_limit_s` integer: Wall-clock ceiling in seconds, measured from when the contract was first sealed (an update does not restart the clock). Ceilings are REPLACED, not merged: omitting `time_limit_s` removes any time ceiling the live contract had. To keep it, send it again. A value that is not a positive integer is a 400.
    - `mode` 'nudge' | 'enforce': Governance mode for the new version. Unlike the ceilings, omitting `mode` keeps the live contract's mode (the gateway carries it over); an explicit value, including `nudge`, replaces it. Any other value is a 400.
  - `expected_hash` string: Optimistic-concurrency guard: the hash the caller last read. A live contract that has moved past it is a 409 `version_conflict`.
  - `expected_version` number: Optimistic-concurrency guard: the version the caller last read (alternative to expected_hash).

## Response `200`

The contract was updated. Returns the new version and both hashes.

- object
  - `conversation_id` string, required
  - `agent` object, required
    - `id` string, required
    - `name` string, nullable, required
  - `org_id` string, required
  - `contract` unknown, required
  - `version` integer, required: The new contract version.
  - `hash` string, required: The new contract's hash.
  - `previous_hash` string, nullable, required: The prior version's hash (`updated_from`).
  - `sealed_at` string, date-time, required

## Other responses

- `400`: Missing `agent_id`, an invalid JSON body, or `contract` missing statement/requirements/allowed_paths/forbidden_paths.
- `401`: Authentication required
- `403`: The caller is not owner/admin in the resolved org (`forbidden`), or presented a SESSION's own credential — gateway-only scopes, or `kind: 'session'` — which is always refused here (`session_credential_forbidden`).
- `404`: No org for the caller, the agent does not exist / is not in the resolved org (`agent_not_found`), or the gateway has no sealed contract on record for this conversation (`no_seal`).
- `409`: `version_conflict` — the live contract has moved since `expected_hash`/`expected_version`; `error.details.current` is the live record.
- `429`: Rate-limit exceeded. The global per-IP limiter (100 requests/minute, applied to every `/v1/*` route) rejected this request. Back off until the window resets — `Retry-After` carries the cooldown in seconds and `X-RateLimit-Reset` the absolute reset time.
- `500`: Server error — request was well-formed but the server failed to fulfill it. Typically a downstream dependency (DB / RPC / external API) returned an unexpected error.
- `503`: The agent lookup failed (retryable), or the Mnemom gateway is unset/unreachable or could not durably write the update (`gateway_unavailable` / `update_unavailable`).

---

[API](https://skmtc.dev/mnemom/apis/mnemom-api.md) · [All operations](https://skmtc.dev/mnemom/apis/mnemom-api/llms.txt) · [OpenAPI document](https://skmtc.dev/mnemom/apis/mnemom-api/revisions/e13923bd55ef?raw)
