Skip to content

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.

EffectRouteCLIAgentsMCP
Readyesyesyesyes
Writeyesyesyesyes
Destructiveyesyesafter a confirmationno
Externalyesyesafter a confirmationno
Which caller reaches which effect, once #[Expose] has opened its surface; what the person sees while a call waits is in Confirmations and forms.

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.

a model may call it
  • 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
each agent call waits for the person
  • 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
The line above each pair says what a model may do with it.
app/Actions/DeletePost.php
php
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.

Always model-driven
  • Agenta laravel/ai tool call
  • Mcpan MCP client
Only inside a tool call
  • Httpa route
  • Consoleactions:run
  • Queuea queued run
  • Systemwebhooks, schedules
The six surfaces, and when a call on each is model-driven.

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
The forms of #[Expose], and the surfaces each one opens.

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.

Released under the MIT License.