Shadway

Core concepts / Mandates

Mandates

A mandate is one job you hand to Shadway, along with the rules it has to follow. You create a mandate, watch it, and wait for it to finish.

An agent is reusable. A mandate is one job. The same agent can collect quotes under one mandate and place orders under another, each with its own goal, its own limits, and its own record of what happened.

Guidance and limits

A mandate keeps two things separate: what you want done, and what the agent is allowed to do. The objective and guidance tell the agent what you want, and the model reads them. The authority sets the limits, and Shadway enforces them on every action before it runs.

Steers the agentRead by the model, never enforced
  • objectiveWhat the work should accomplish
  • guidanceHow you’d like it done. It can never block an action
  • deadlineWhen the objective is due. Informational only
Enforced by ShadwayChecked on every single action
  • authorityCapabilities, budgets, boundaries, and approval rules
  • expires_atThe authority cutoff. Past it, nothing executes and the mandate ends expired

Putting "never spend more than $8,000" in the guidance doesn't stop anything. Setting authority.spend.maximum does. If something has to be true, put it in the authority. If it's just a preference, put it in the guidance.

deadline and expiresAt work the same way. The deadline tells the agent when the job is due, which affects how it plans, not what it's allowed to do. expiresAt is a hard cutoff: once it passes, no action runs and the mandate ends expired.

Create a mandate

create-mandate.ts
const mandate = await shadway.mandates.create({
  agent: agent.id,
  objective:
    "Buy 200 units of part #48-221 for less than $8,000 total, " +
    "including shipping.",
  guidance: [
    "Compare at least two suppliers before ordering.",
    "Ask for written confirmation of agreed terms.",
  ],
  authority: {
    capabilities: ["email.send", "mailbox.read_thread", "procurement.create_po"],
    spend: {
      maximum: { amount: 799_999, currency: "usd" },
    },
    maximumActions: 40,
    capabilityLimits: [
      { capability: "procurement.create_po", maximumActions: 1 },
    ],
    approval: {
      requiredFor: ["procurement.create_po"],
    },
    successCriteria: [{ fact: "amount_minor", max: 799_999 }],
  },
  metadata: { purchaseRequest: "PR-1042" },
});

Money amounts are in the smallest unit, with a lowercase currency code: 799,999 in usd is $7,999.99. procurement.create_po is an example of a custom capability. Only grant capability names that exist in your workspace, and use the full action name like email.send. A bare family name like email can't be granted.

Authority fields

Every field is optional. Leave one out and that limit doesn't apply.

Budgets. spend.maximum caps total spending. Any action that spends money has to say how much, and if it doesn't, Shadway blocks it rather than treating it as zero. spend.approvalThreshold (which has to be below the maximum) makes any single action above that amount wait for approval. maximumActions limits how many actions with an effect the agent can take. maximumReadActions is a separate budget for read-only actions, so checking a delivery status doesn't count against it.

Per-capability limits. capabilityLimits sets a limit on a single capability: a maximum count, a rate like { maximum: 3, interval: "1h" }, or both. Once the limit is hit, Shadway blocks the action and the agent works around it.

Boundaries. prohibitedActions lists actions that can never run under this mandate. Shadway checks this list first, so no other rule can turn them back on. quietHours blocks actions during set hours in a given timezone. rules limit the values an action can use. For example, a rule can allow procurement.create_po only when quantity is 500 or less. A rule can only tighten a capability the mandate already grants, and an action that leaves out a limited value is blocked.

Approvals. approval.requiredFor lists actions that need a person to sign off every time, no matter the amount. See Approvals.

Done. successCriteria set what counts as finished, based on confirmed facts. See Verification.

Mandate statuses

Mandate statusLive and terminal statuses
While work is live
Terminal: the mandate is over
working

The agent is taking a turn: reading, reasoning, and proposing actions.

Shadway sets the status, not you. If a wait for a person times out, the mandate ends up paused, not failed. You can resume it, and it keeps everything it learned.

Pause, resume, and cancel

These are commands you send, not statuses you set. Each one only works from certain statuses. Send one that doesn't apply and you get a conflict error instead of it being ignored.

control.ts
const work = shadway.mandate(mandate.id);

await work.pause({ reason: "Procurement is reviewing the specification." });
await work.resume({ reason: "The specification is confirmed." });
await work.cancel({ reason: "The part is no longer needed." });

These are three separate actions. Send the one that fits what you decided, not all three in a row. Pause stops new work at the next safe stopping point. The status changes once the work actually stops, not the moment you call it, and anything already sent to an outside system isn't undone. Cancel works from any non-final status and stops further work, but it doesn't undo an email already sent or an order already placed.

Change the limits with an amendment

You don't edit a mandate's limits in place. You send an amendment, which replaces the whole set of limits, records who changed them and why, and takes effect at the mandate's next step, even if it's in the middle of running.

amend.ts
const current = await work.get();

const amendment = await work.amend({
  authority: {
    ...current.authority,
    spend: { maximum: { amount: 899_999, currency: "usd" } },
  },
  reason: "Approved budget increase for expedited shipping.",
  expectedVersion: current.authorityVersion,
});

console.log(amendment.version); // The new authority version.

expectedVersion keeps two edits from clashing: if someone amended the mandate after you read it, yours is rejected with a conflict instead of quietly overwriting theirs. Amendments can't be changed after the fact. The full history stays, and every action records which version of the limits it ran under.

Continue building

On this page