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.
objectiveWhat the work should accomplishguidanceHow you’d like it done. It can never block an actiondeadlineWhen the objective is due. Informational only
authorityCapabilities, budgets, boundaries, and approval rulesexpires_atThe authority cutoff. Past it, nothing executes and the mandate endsexpired
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
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" },
});maxActions := int64(40)
poLimit := int64(1)
maxAmount := float64(799_999)
mandate, err := client.Mandates.Create(ctx, shadway.MandateCreateParams{
Agent: agent.ID,
Objective: "Buy 200 units of part #48-221 for less than $8,000 total, " +
"including shipping.",
Guidance: []string{
"Compare at least two suppliers before ordering.",
"Ask for written confirmation of agreed terms.",
},
Authority: &shadway.Authority{
Capabilities: []string{
"email.send", "mailbox.read_thread", "procurement.create_po",
},
Spend: &shadway.SpendAuthority{
Maximum: shadway.Money{Amount: 799_999, Currency: "usd"},
},
MaximumActions: &maxActions,
CapabilityLimits: []shadway.CapabilityLimit{
{Capability: "procurement.create_po", MaximumActions: &poLimit},
},
Approval: &shadway.ApprovalAuthority{
RequiredFor: []string{"procurement.create_po"},
},
SuccessCriteria: []shadway.SuccessCriterion{
{Fact: "amount_minor", Max: &maxAmount},
},
},
Metadata: &shadway.Metadata{"purchaseRequest": "PR-1042"},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(mandate.ID, mandate.Status)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
workingThe 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.
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." });work := client.Mandate(mandate.ID)
pauseReason := "Procurement is reviewing the specification."
if _, err := work.Pause(ctx, &shadway.MandateCommandParams{Reason: &pauseReason}); err != nil {
log.Fatal(err)
}
resumeReason := "The specification is confirmed."
if _, err := work.Resume(ctx, &shadway.MandateCommandParams{Reason: &resumeReason}); err != nil {
log.Fatal(err)
}
cancelReason := "The part is no longer needed."
if _, err := work.Cancel(ctx, &shadway.MandateCommandParams{Reason: &cancelReason}); err != nil {
log.Fatal(err)
}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.
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.current, err := work.Get(ctx)
if err != nil {
log.Fatal(err)
}
authority := current.Authority
authority.Spend = &shadway.SpendAuthority{
Maximum: shadway.Money{Amount: 899_999, Currency: "usd"},
}
amendment, err := work.Amend(ctx, shadway.MandateAmendmentCreateParams{
Authority: authority,
Reason: "Approved budget increase for expedited shipping.",
ExpectedVersion: current.AuthorityVersion,
})
if err != nil {
log.Fatal(err)
}
fmt.Println(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.