Effects and surfaces
An action says what it does to the world, a call says where it comes from, and the two together decide whether the call runs.
| Effect | Route | CLI | Agents | MCP |
|---|---|---|---|---|
| Read | yes | yes | yes | yes |
| Write | yes | yes | yes | yes |
| Destructive | yes | yes | after a confirmation | no |
| External | yes | yes | after a confirmation | no |
Four effects
Every action declares one $effect. It picks the token ability a call needs, and whether a model may call the action at all. An action without one opens no remote surface and fails actions:check.
- Effect::ReadReadchanges nothingtoken ability:
actions:read - Effect::WriteWritechanges the actor's own data, which the actor could enter again, and affects nobody else yettoken ability:
actions:write
- Effect::DestructiveDestructiveremoves something other people rely on, or the actor cannot recreatetoken ability:
actions:destructive - Effect::ExternalExternalreaches people or systems outside the actor's own data: an email, a payment, a webhooktoken ability:
actions:external
protected ?Effect $effect = Effect::Destructive;See Effects in Concepts for when to use which, and Security for the abilities.
Six surfaces
A surface is where a call comes from. Agent and Mcp are model-driven, and so is any call made inside a laravel/ai tool call or an MCP request; a queued run keeps the answer of the call that queued it. A model-driven call reaches only Read and Write, except an agent's call the person confirms, and needs a signed-in person unless the action sets $guests, as a public support bot would. See Surfaces in Concepts.
- Agenta laravel/ai tool call
- Mcpan MCP client
- Httpa route
- Consoleactions:run
- Queuea queued run
- Systemwebhooks, schedules
Exposure
#[Expose] is the only way onto a network or model surface; in-process calls and routes you write never read it, see Doors. Bare, it opens every surface the action can use and quietly skips the rest: agents and MCP for a Destructive or External action, agents while laravel/ai is not installed, MCP for an action without a description, and the route for an action with an agentSchema(). Naming a surface the rules refuse is an error instead, except a toolset named for a Destructive or External action.
#[Expose]every surface the effect and shape allow- Route: open
- Agents: open
- MCP: open
#[Expose(web: true)]the generated route only- Route: open
- Agents: closed
- MCP: closed
#[Expose(agents: ['support'])]the "support" toolset only- Route: closed
- Agents: support: open
- MCP: closed
#[Expose(web: true, agents: ['default'])]both- Route: open
- Agents: default: open
- MCP: closed
#[Expose(mcp: true)]MCP only- Route: closed
- Agents: closed
- MCP: open
no #[Expose]run(), attempt() and actions:run still reach it- Route: closed
- Agents: closed
- MCP: closed
actions:list and actions:check show every decision with its reason, and the switches surfaces.web, surfaces.agents and surfaces.mcp close a surface for the whole app. See Exposure in Concepts for the rules in full.