Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 1 addition & 10 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,5 @@ target/
/.idea/

**/graphql.config.yml
.agents/
/.claude/
/openspec/
/coverage-reports/
.cursor/
/brand/
/scripts/
/AGENTS.md
/DESIGN.md
/packages/
unpackagedMetadata/
.claude/settings.local.json
7 changes: 7 additions & 0 deletions .husky/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Runs the push check from this checkout. On a branch without it, such as
# public-release, runs the copy committed on main. Clones without it skip the check.
if [ -x scripts/check-public-push.sh ]; then
exec scripts/check-public-push.sh "$@"
elif git cat-file -e main:scripts/check-public-push.sh 2>/dev/null; then
exec bash -c "$(git show main:scripts/check-public-push.sh)" check-public-push "$@"
fi
5 changes: 4 additions & 1 deletion cumulusci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ flows:
options:
command: sf package version create --package PlutoConsole --code-coverage --installation-key-bypass --wait 90
# Run with --org qa. Uses the newest package versions, so run build_packages first.
packaged_org:
qa_org:
description: Sets up a scratch org like dev_org, but installs Pluto Core, Model Adapters, and Console from their newest unlocked package versions instead of deploying their source.
steps:
1:
Expand Down Expand Up @@ -256,3 +256,6 @@ orgs:
dev:
config_file: orgs/dev.json
days: 30
qa:
config_file: orgs/dev.json
days: 30
36 changes: 25 additions & 11 deletions docs-starlight/src/content/docs/approvals/policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,40 @@ A policy is an Apex class that a developer writes. It implements `IToolAuthoriza

## Turn it on

1. Ask your developer for the policy class and which tools it covers.
2. On the agent, pick the class in **Tool Authorization Policy Class**.
3. On each tool the policy covers, set **Approver Type** and leave **Always Require Approval** unchecked.
1. Ask your developer for the policy class, which tools it covers, and which queues or users it names.
2. Check that each queue the policy names exists and lists Agent Approval under **Supported Objects**.
3. On the agent, pick the class in **Tool Authorization Policy Class**.
4. On each tool the policy covers, set **Approval** to **Decided by Policy**. Leave Approver Type, Approver User, and Approver Queue blank. The policy names the approver.
5. On each of those tools, choose **When Approval Expires**. Optionally set **Approval Timeout (Minutes)** for calls where the policy gives no deadline.

## What a policy can decide
The tool page saves without an error when the setup is valid. It refuses a Decided by Policy tool on an agent with no policy class. It also refuses clearing the policy class while the agent has an active Decided by Policy tool, and names those tools.

Core asks the policy about every tool call, before it runs.
## Who decides

| Decision | What happens |
Core asks the policy only about tools whose Approval is Decided by Policy. Tools set to Not Required or Always Required never reach it.

| Policy answer | What happens |
| --- | --- |
| Allow | The call runs, unless the tool has Always Require Approval checked. |
| Allow | The call runs. |
| Deny | The call does not run. The agent gets `TOOL_AUTHORIZATION_DENIED` with the policy's reason and continues. |
| Require Approval | The call waits for approval. The policy gives the reason, and may set a different deadline (1 minute to 7 days) or a different approver, such as a finance queue for large amounts. |
| Require Approval | The call waits for the user or queue the policy names. The policy must name one. The deadline is the policy's, then the tool's Approval Timeout, then 1440 minutes (24 hours). |

A deadline from the policy replaces the tool's Approval Timeout, so a large refund can get a shorter deadline than a small one. When Approval Expires on the tool applies to every call.

Core runs the policy again before an approved call runs. A deny still stops the call. Any other answer lets the approved call run, so a call never waits for a second approval.

A policy can only narrow what the agent may do. It cannot skip a permission check or approve a call.

Core's configuration check, `AgentConfigurationValidationService.validate`, returns the warning `POLICY_NEVER_RUNS` when the agent has a policy class but no active tool whose Approval is Decided by Policy.

## See why a call waited

A policy can only narrow what the agent may do. It cannot skip a permission check, and it cannot approve a call.
Open the run's **Run Waiting** event. Its `requiredBy` key is `Tool Setting` for an Always Required tool, or `Policy` for a Decided by Policy tool.

## When a policy fails

| Error code | Cause | Fix |
| --- | --- | --- |
| `TOOL_AUTHORIZATION_POLICY_INVALID` | The policy asked for approval with no reason, or a deadline out of range. | Ask the developer to fix the class, then rescue the run. |
| `APPROVAL_ASSIGNMENT_UNRESOLVED` | Neither the policy nor the tool named anyone who may approve. | Set Approver Type on the tool, then rescue the run. |
| `TOOL_AUTHORIZATION_POLICY_INVALID` | The policy asked for approval without naming a user or queue, with no reason, or with a deadline out of range. It may also have named a user Id that is not a user. | Ask the developer to fix the class, then rescue the run. |
| `APPROVAL_ASSIGNMENT_UNRESOLVED` | The queue the policy named does not exist or does not support Agent Approval, or the user it named cannot decide approvals. The error names the queue or user. | Create the queue, add Agent Approval to its **Supported Objects**, or give the user `AgentKernelHumanDecision`. Then rescue the run. |
| `INITIATING_USER_APPROVAL_UNSUPPORTED` | The policy asked for approval on an agent without an identity. | Give the agent an identity, or change the policy to allow or deny. |
20 changes: 15 additions & 5 deletions docs-starlight/src/content/docs/approvals/require.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,29 @@ A tool can wait for a person to approve each call before it runs. The run waits,
Approvals need an agent with an [Agent Execution Identity](/setup/execution-identity/). An agent without one runs as the starter and cannot wait for someone else.
:::

## Choose Approval

Each Agent Tool has an **Approval** field. It names who decides each call.

| Approval | What happens |
| --- | --- |
| Not Required (the default) | Every call runs. The agent's policy is not asked. |
| Always Required | Every call waits for the tool's Approver Type. The agent's policy is not asked. |
| Decided by Policy | The agent's policy decides each call: run it, deny it, or wait for a user or queue the policy names. See [Approve by rule](/approvals/policy/). |

## Turn it on

On the Agent Tool:

| Field | What to enter |
| --- | --- |
| Always Require Approval | Checked. Every call waits for approval. |
| Approval | Always Required. |
| Approver Type | Who decides. See below. |
| Approver User or Approver Queue | Depends on Approver Type. |
| Approval Timeout (Hours) | How long the approver has. Blank is 24. |
| When Approval Expires | **Deny and Continue** (the default): the agent is told the call was not approved and carries on. **Pause for Operator**: the run pauses with `WAIT_EXPIRED` for an operator. |
| Approval Timeout (Minutes) | How long the approver has, from 1 to 10080 (7 days). Blank is 1440 (24 hours). |
| When Approval Expires | Required. **Deny and Continue**: the agent is told the call was not approved and carries on. **Pause for Operator**: the run pauses with `WAIT_EXPIRED` for an operator. |

Leave **Always Require Approval** unchecked if only some calls need approval, such as refunds over $200. A policy class then decides call by call, and still sends approvals to the Approver Type. See [Approve by rule](/approvals/policy/).
The tool page saves without an error when the setup is valid. It refuses an approver, Approval Timeout, or When Approval Expires on a tool whose Approval is Not Required.

## Approver types

Expand All @@ -44,7 +54,7 @@ The person who started the run may approve it when they are the assigned approve

## What the agent tells the approver

For a tool that needs approval, Core asks the model for the change, the facts behind it, and what the approver should check. The approver sees this as the agent's explanation. Core does not check it, so treat it as a claim, not proof.
For a tool whose Approval is Always Required or Decided by Policy, Core asks the model what the call will do, the facts behind it, and each condition it did not confirm with a tool. The approver sees this as the agent's explanation. Core does not check it, so treat it as a claim, not proof.

## What the run looks like

Expand Down
2 changes: 1 addition & 1 deletion docs-starlight/src/content/docs/build/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Core checks the setup when you save it. A mistake shows as a field error on save
| Field | Default | Notes |
| --- | --- | --- |
| Agent Execution Identity | Blank: the run acts as whoever starts it. | Set it for approvals, background work, and long waits. See [Create an execution identity](/setup/execution-identity/). |
| Tool Authorization Policy Class | Blank: no policy. | An Apex class that allows, denies, or sends tool calls for approval by rule. See [Approve by rule](/approvals/policy/). |
| Tool Authorization Policy Class | Blank: no policy. | An Apex class that allows, denies, or sends tool calls for approval by rule. It decides only tools whose Approval is Decided by Policy. See [Approve by rule](/approvals/policy/). |

## Limits

Expand Down
2 changes: 1 addition & 1 deletion docs-starlight/src/content/docs/build/record-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ The tool returns the new record's Id.
| `defaultFieldValues` | No | Values every update sets. The agent cannot change them. |
| `anchorRecordOnly` | No | `true` updates only the record the run started from, and the agent does not send an Id. Blank or `false` lets the agent pick any record of the object the run's user can edit. |

Set `anchorRecordOnly` to `true` whenever the agent reads customer text, such as emails or case comments. That text could otherwise ask the agent to change a different record. Without it, Core warns when you validate an agent that has an Agent Execution Identity, unless the tool has Always Require Approval checked.
Set `anchorRecordOnly` to `true` whenever the agent reads customer text, such as emails or case comments. That text could otherwise ask the agent to change a different record. Without it, Core warns when you validate an agent that has an Agent Execution Identity, unless the tool's Approval is Always Required.

## Run values

Expand Down
2 changes: 1 addition & 1 deletion docs-starlight/src/content/docs/build/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Developers can add more tool types in Apex. See [Extend Pluto](/extend/).
| Input Schema | The inputs the model may send, as JSON Schema. Leave it blank for a tool type that builds its inputs: every Core tool type, and Search Knowledge, Run Report, and the channel tools. Fill it in for Call API, Read from API, the MCP tools, and a custom type that does not build its inputs. |
| Sort Order | Lower numbers are listed to the model first. |
| Is Active | Unchecked by default. Check it to give the tool to the agent. |
| Always Require Approval and the approval fields | See [Require approval](/approvals/require/). |
| Approval and the approval fields | See [Require approval](/approvals/require/). |
| Allowed Sensitive Data Categories | Leave empty unless the tool needs real values, such as a real email address to send to. See [Sensitive data](/data/sensitive/). |

In Pluto Console, the tool editor shows a form for the settings, so most tools need no JSON.
Expand Down
4 changes: 2 additions & 2 deletions docs-starlight/src/content/docs/reference/pause-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ A paused run shows its code in **Error Code**. After you fix the cause, rescue t
| `UNKNOWN_UNRESOLVED` | Nobody reconciled an Unknown call before the run's payload retention ended. | Reconcile the call, or cancel the run. See [Settle an Unknown call](/operate/unknown/). |
| `WAIT_EXPIRED` | An outside wait, or an approval set to **Pause for Operator**, ran out of time. | Rescue to let the agent continue without it, or cancel. |
| `TOOL_BINDING_CHANGED` | A tool changed or was deactivated after the call started. | Restore the tool as it was, or cancel the run and start a new one. |
| `TOOL_AUTHORIZATION_POLICY_INVALID` | The agent's policy asked for approval with no reason or a deadline out of range. | Fix the policy class, then rescue. |
| `APPROVAL_ASSIGNMENT_UNRESOLVED` | Nobody eligible could be found to approve the call. | Set Approver Type, Approver User, or Approver Queue on the tool, then rescue. |
| `TOOL_AUTHORIZATION_POLICY_INVALID` | The agent's policy asked for approval without naming a user or queue, with no reason, or with a deadline out of range. A tool whose Approval is Decided by Policy also pauses with this code when its agent has no policy class. | Fix the policy class, then rescue. |
| `APPROVAL_ASSIGNMENT_UNRESOLVED` | Nobody eligible could be found to approve the call. | For an Always Required tool, fix Approver Type, Approver User, or Approver Queue. For a Decided by Policy tool, fix the queue or user the policy names. Then rescue. |
| `INITIATING_USER_APPROVAL_UNSUPPORTED` | A call needed approval, but the agent has no execution identity. | Give the agent an identity, or change the policy. Then start a new run. |

## Tool call results the agent sees
Expand Down
76 changes: 55 additions & 21 deletions force-app/contracts/EXTENSIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,8 @@ Errors and outcomes:
handles failures as it does for `Stays in This Org`: a callout failure or lost
worker repeats the call within the run's retry limits, an unexpected
exception returns to the model, and a malformed response pauses the run.
The tool is not asked for `_rationale` unless an approval or policy needs
it. A tool that throws `OutcomeUnknownException` is still `Unknown`.
The tool is not asked for `_rationale` unless its Approval is
`Always Required` or `Decided by Policy`. A tool that throws `OutcomeUnknownException` is still `Unknown`.
- `Changes Outside Systems`, or a value Core does not recognize: an
unexpected exception, a malformed or oversized response, or a lost worker
makes the outcome `Unknown`. Core repeats the call only when the tool asks
Expand Down Expand Up @@ -308,27 +308,61 @@ configuration blank.
## `IToolAuthorizationPolicy`

Policies follow the same query-only rule and governor reserve as context
providers. Core evaluates the policy for every call; return `Allow`, `Deny`,
or `RequireApproval`.

- `Deny` always stops the call. The model gets `TOOL_AUTHORIZATION_DENIED`
with your `reason` and continues, so write the reason for the model.
- If the tool's `Always Require Approval` is checked, the call waits for approval
even on `Allow`. Otherwise `Allow` runs it and `RequireApproval` waits.
- `RequireApproval` needs a nonblank `reason`. Optional `timeoutSeconds` is 60
through 604,800; blank uses the tool's Approval Timeout. Optional
`approverId` names a user or queue that decides this call instead of the
tool's approver, for example to escalate by amount.
providers. Core evaluates the policy only for calls of tools whose Approval is
`Decided by Policy`, after it checks the call's arguments against the tool's
Input Schema. Calls of `Not Required` and `Always Required` tools never reach it. Build the answer from the request:

```apex
public AgentKernelModels.ToolAuthorizationResponse evaluate(AgentKernelModels.ToolAuthorizationRequest request) {
if (request.toolType != 'NorthwindIssueRefund') {
return request.allow();
}
Decimal amount = request.decimalInput('amount');
if (amount == null) {
return request.deny('Send the refund amount.');
}
if (amount <= 25) {
return request.allow();
}
return request.requireApproval('The refund is over 25.').byQueue('Finance_Approvers');
}
```

- Match on `request.toolType`, the API name of the tool's Agent Tool Type.
`toolName` is the tool's API name on the agent, and an admin can change it.
A policy that matches on `toolName` stops covering a tool when it is renamed.
- `request.inputValue(name)` returns one top-level input. `request.decimalInput(name)`
returns a number input, or null when it is missing or not a number.
- `request.deny(reason)` always stops the call. The model gets
`TOOL_AUTHORIZATION_DENIED` with your reason and continues, so write the
reason for the model.
- `allow()` runs the call, and `requireApproval(reason)` makes it wait.
- `requireApproval` needs a nonblank reason. The approver sees it. Describe
the call, such as "The refund is over 200", not who should approve it.
- `requireApproval` returns `AgentKernelModels.ToolApprovalResponse`.
Name who approves this call with `.byQueue(apiName)` or `.byUser(userId)`.
Each replaces the other. The tool has no approver of its own, so a hold that
names neither pauses the run with `TOOL_AUTHORIZATION_POLICY_INVALID`.
- `.withTimeoutMinutes(minutes)` sets this call's deadline in whole minutes,
1 through 10,080 (7 days). It replaces the tool's Approval Timeout, so a
large refund can get a shorter deadline than a small one. Without it, the
tool's Approval Timeout applies, or 24 hours when that is blank. The tool's
When Approval Expires always applies.
- Core runs the policy again before an approved call runs. A deny still stops
it. Any other answer, including a hold that names someone else, lets the
approved call run, so a call is never approved twice.
- Core pauses the run with `TOOL_AUTHORIZATION_POLICY_INVALID` for a blank
reason or out-of-range timeout, and with `APPROVAL_ASSIGNMENT_UNRESOLVED`
when neither policy nor tool names an eligible approver.
- Runs of agents without an identity cannot wait: `RequireApproval` pauses
them with `INITIATING_USER_APPROVAL_UNSUPPORTED`. Return `Allow` or `Deny`
reason, a timeout outside 1 through 10,080 minutes, or a `byUser` Id that is
not a user.
- Core pauses the run with `APPROVAL_ASSIGNMENT_UNRESOLVED` when the named
queue does not exist or does not support Agent Approval records, or the named
user cannot decide approvals. A missing queue never falls back to another
approver.
- The `Run Waiting` event records `requiredBy`: `Tool Setting` for an
`Always Required` tool, or `Policy`.
- Runs of agents without an identity cannot wait: `requireApproval` pauses
them with `INITIATING_USER_APPROVAL_UNSUPPORTED`. Return `allow()` or `deny()`
when `request.executionIdentityId` is null.
- Upgrading from `executionMode`: `executionMode == 'Delegated Principal'`
becomes `executionIdentityId != null`, and `'Initiating User'` becomes
`executionIdentityId == null`. `ExecutionPrincipal`'s constructor takes the
identity Id in place of the mode.

Core creates the Agent Approval and decides who may approve it; see
[Require approval](https://iamsonal.github.io/aiAgentStudio/approvals/require/).
Expand Down
Loading
Loading