Run your own logic. Every write still waits its turn.

Some work is more than one call: read the order, check it, then refund and email. Write that as a function in JavaScript or Python. Anlyon runs it with no network and no keys, plans its writes, and sends them only once the exact list is approved.

A few lines on your server, then connect a user.

  1. 1.Write itA run(input, anlyon) function. Its only way out is the anlyon binding.
  2. 2.Publish itAnlyon reads the actions it calls into an allow-list. A version never changes.
  3. 3.Plan a runReads run for real. Each write is recorded, not sent, and the list waits for a yes.
  4. 4.Apply the planOn the yes, each write must match the plan in order, or nothing more is sent.

Code that holds a key can do anything the key allows, whatever you meant it to do. A hosted function holds no key and no network, so it can change only what its version's actions allow.

Less risk

No network, no secrets, no action off the list. Writes leave only as the plan that was approved.

Less cost

No sandbox, job runner or plan-and-approve flow of your own to build for multi-step work.

Less effort

Plain JavaScript or Python. Limits, approvals and receipts come from the actions it calls.

Your code decides. Anlyon decides what leaves.

The guarantee doesn't come from reading your code. It comes from where the code runs and what it may call.

No network, no secrets, one way out

fetch() and sockets throw, and there are no environment variables or secrets. A function changes things one way: anlyon.invoke(action, input) and anlyon.read(action, input), each through the same limits, policies, halt switch and receipts as a call your agent makes.

  • A read must be a GET action, or it is refused with NOT_A_READ
  • Keys stay in the actions, added when Anlyon sends the call
  • Run for one of your users, every call uses their accounts and limits

It calls what its version lists, and nothing else

Each version carries allowedActions, read from the literal calls in the source when you publish, or listed by you. A call to anything else is refused with ACTION_NOT_ALLOWED and nothing is sent. A source that builds an action name at run time must list its actions.

  • Checked where the function runs, and again by the API
  • Publishing adds a new version; an old one never changes
  • Approvers see what the source reads and writes beside the plan

Approve the exact list of writes

A run plans by default. Reads run for real, and each write is recorded and answered { planned: true }. A plan with writes waits for one approval bound to its digest: the version, the input, the user and every write in order. The apply run sends a write only when it equals the plan's next one.

  • A write that differs ends the run before it is sent: RUN_DIVERGED
  • A plan is applied once; a second try answers PLAN_ALREADY_APPLIED
  • The user decides in your app when every write's policy says so

Every run is bounded and recorded

Each version sets its budgets: action calls, CPU time, calls through the binding and wall time. A run that runs out ends budget_exceeded and says which. The run receipt keeps the version and the hash of its source, the input's hash, the user, the plan digest and every call in order.

  • Each write that was sent has its own action receipt
  • Or run straight through, up to maxWrites to the actions you name
  • Run it from the SDKs, the API, or as an MCP tool

Write a function. Then plan a run for one user.

The function reads the order, checks it, and asks for two writes. Your server plans a run for the signed-in user, who approves both writes in your app before anything is sent.

refund-with-checks.js
export default async function run(input, anlyon) {  const lookup = await anlyon.read("get-order", { order: input.order });  if (!lookup.ok) return { refunded: false, reason: lookup.error.code };  const order = lookup.result.json;  if (input.amount > order.total) return { refunded: false };  await anlyon.invoke("refund-order", { charge: order.charge, amount: input.amount });  await anlyon.invoke("send-email", { to: order.email, subject: "Your refund" });  return { refunded: true };}// On your server: plan it for the signed-in user.const { data: plan } = await anlyon  .forUser(session.userId)  .functions.run("refund-with-checks", { input: { order, amount } });// plan.status is "awaiting_approval", with both writes in plan.plan.writes

The details. What a run can and can't do.

  • Budgets per version

    maxActionCalls, maxCpuMs, maxSubrequests and timeoutMs, each with a default and a range.

  • Who decides a plan

    The team in the console or Slack, or the user in your app when every write's policy is decided by the end user.

  • Keys and scopes

    functions:write publishes, functions:run with actions:invoke runs, functions:read reads. None is granted by default.

  • Delete keeps the record

    A deleted function refuses new runs. Its versions and runs stay, as the record of what it did.

Let your agent act for your users, safely.

A few lines of server code, then send a user a connect link. Nothing to host.

$ claude mcp add --transport http anlyon https://api.anlyon.com/mcp