Farther ShoreDocs
Go to Farther Shore
What is FartherShore
Install the CLI
Quickstart
Core concepts
The @Product class
Meters & resources
Features & routes
Capabilities & entitlements
Plans & pricing
The Manifest IR
Bring your own backend
Transport modes
Metering & verification
Runtime tokens
Frontend SDK
Root & data components
Auth & sessions
Entitlement gates
Connect Stripe
Subscriptions & usage
Plan changes & grandfathering
Billing strategies
Apply & deploy
Environments
Migrations
Docs versions & archive
Operate with an agent
Operation classes
The three classesThe single invariantWhat an agent may and may not changeWhy two classes for backendsThe DRAFT exceptionHow to react to MANAGEDBYCODE
MCP server
End-to-end via CLI/MCP
CLI reference
@farthershore/product
@farthershore/backend
@farthershore/farthershore-js
Environment variables
Response & deny codes
Add a metered capability
Gate a feature
Change a price
Prepaid credits
Meter AI tokens
Operate via an agent
Prepare for launch
Status
Docs/Automate with agents/Operation classes

Operation classes

Contract vs operate vs dual — the formal boundary deciding what an agent may change via the API/MCP and what it must change in the repo.

PreviousOperate with an agentNextMCP server

Every platform operation falls into exactly one of three classes. The class answers one question an agent asks constantly: can I change this with an API/MCP call, or must I edit the product repo and push? The taxonomy is a single typed source of truth (@farthershore/contracts operation-class.ts) that core's guard, the CLI, the MCP server, and the dashboard all consume — so the boundary can never be reasoned about inconsistently.

export type OperationClass = "contract" | "operate" | "dual";

The three classes

contract — managed as code. Declarative product state that round-trips with the manifest / ProductSpec and is canonical in the product's GitHub repo: plans, pricing, routes/features, meters, capabilities, policies, gateway config, backend definitions, webhook endpoints, and the contractual product fields. Once the product is ACTIVE and repo-linked, the core API returns 409 MANAGED_BY_CODE on writes (the DRAFT creation window is the seed-the-repo exception). Over MCP: read-only. An agent changes these by editing the manifest and pushing — never by an out-of-band call.

operate — runtime operation. An imperative action on the running platform that is never committed to the repo: runtime tokens, usage reads, readiness/status, persona/test bootstrap, build and publish triggers, frontend deploys, runtime knobs, and brand/presentation edits. Over MCP: read + write.

dual — repo or API. Reconciles via either the repo or the API/MCP; both are first-class and converge to the same state. Preview environments (a git branch ⇄ an environment) are the canonical example. Over MCP: read + write.

// The decisive test (contract vs operate):
// "Does this state live in the manifest / round-trip with ProductSpec, such
//  that publishing from the repo would reconcile or overwrite an out-of-band
//  edit?"
//   Yes                  → contract
//   No (credential /     → operate
//      ephemeral / action)
//   Both repr. + path    → dual
ClassCanonical homeMCP may write?
contractrepono
operateapiyes
dualbothyes

The single invariant

One rule governs every MCP/API surface: a tool that writes may not touch contract-class state. Reads are always allowed. The check is shared code:

export function mcpToolClassError(
  operationClass: OperationClass,
  sideEffect: "read" | "write",
): string | null {
  if (sideEffect === "write" && !mcpMayWrite(operationClass)) {
    return (
      `An MCP tool that writes cannot have operationClass "${operationClass}": ` +
      `${operationClass}-class state is managed as code in the product repo ` +
      `(canonical home: ${canonicalHome(operationClass)}). Make the tool ` +
      `read-only, or author the change in the manifest and push instead.`
    );
  }
  return null;
}

So fs_plan_list exists (a contract read) but there is no fs_plan_create — creating a plan is a contract write, which lives in the manifest. The CLI still exposes farthershore plan create as a convenience against a DRAFT, but on a published product it returns MANAGED_BY_CODE (exit 4) and you edit the @Plan member instead.

What an agent may and may not change

This is the practical split. A contract change means editing product/product.config.ts and pushing; everything else is a direct call.

OperationClassHow an agent changes it
plan.create / update / deletecontractEdit @Plan in the manifest, push
route.define / feature.definecontractEdit @Feature routes, push
meter.definecontractEdit @Meter / @Requests, push
capability.define / entitlement.definecontractEdit @Capability / @Entitlement, push
gateway.config / rate_limit.updatecontractEdit the manifest, push
backend.definecontractEdit the backend block, push
product.contract_field.updatecontractEdit @Product({ name, origin, … }), push
runtime_token.mint / rotate / revokeoperatefarthershore backend tokens …
usage.readoperatefarthershore usage summary
product.status / frontend.statusoperatefarthershore product status
persona.bootstrapoperatefarthershore persona bootstrap
brand.updateoperatefarthershore product update --display-name …
product.create / publish / rollbackdualfarthershore product create / publish
environment.create / update / deletedualfarthershore env … or a branch push

The contractual product fields are exactly name, baseUrl, sandboxBaseUrl, meters, subscriberChangePolicy, envBranchPrefix. The presentation fields — displayName, description, iconUrl, logoUrl, primaryColor, featuredPlanId — are operate, so an agent may edit branding via the API even on a code-managed product.

Why two classes for backends

Backends split deliberately. backend.define is the ProductSpec backend block (transport, verification, routing) — that is contract, edited in the manifest. backend.create / backend.delete provision a deployment instance (the tunnel lifecycle) and mint runtime tokens — those are operate, done with farthershore backend create / backend tokens mint. Same resource, two classes, because one is the contract and the other is a runtime action.

The DRAFT exception

A product is dual: it is both a repo and a platform record, and product.create is the bridge. During the DRAFT window — before the product goes ACTIVE — the API accepts contractual writes so the agent can seed the repo (this is what plan create does at scaffold time). Once published, the repo is canonical and the guard engages. The mental model: seed via the API, then operate as code.

How to react to MANAGED_BY_CODE

When any call returns exit 4 / 409 MANAGED_BY_CODE, the change you attempted is contract-class. Do not retry the API. Instead:

# 1. Edit the decorated definition (e.g. raise the Pro plan's price).
#    product/product.config.ts → @Plan("pro", { price: { amount: 24900, … } })

# 2. Validate locally — same gates as publish.
farthershore build --format json

# 3. Commit and push; the GitHub bot compiles and applies the change.
git commit -am "raise Pro price" && git push

See the end-to-end walkthrough for the full decorated @Product class, and the MCP page for which operations surface as fs_* tools.