Skip to content

Agents and the copilot ​

An agent receives your actions as tools, and the copilot shows the person each call as a live row, taken from what the pipeline did rather than from what the model says.

example.com/posts
Postsreloaded: posts
Spring meetupdraft
Release notespublished
Hello, worldpublished
Copilot
Person:
Draft a post about the spring meetup.
Draft savedtouches: postsdone
Looking it up…running
A row ends as
  • done
  • refused
  • failed
  • ended
  • declined
A reloadwaits while an editor is dirty
A done row names the page data it touched, and the page reloads it while the turn goes on.

Tools come from toolsets ​

#[Expose] puts an action in a toolset, default unless it names others, and #[UseToolset] picks the toolsets an agent receives. With InteractsWithActions, the tools are built on every turn from actionContext(), for the signed-in person, so an action whose checks before input say no never reaches the list.

toolsets
#[Expose]
create-postdefault
post-statsdefault
delete-postteam
tools this turn
BlogAssistant

#[UseToolset] receives default, built for the signed-in author

create-post
post-stats

Left out: an action whose checks before input say no.

the model calls
create-post
then runs
the pipeline
every check, as the author
the model reads
“Done.”
or “Not done. Rejected: title (max).”
The agent's tools are its toolsets' actions that pass for this person.

The model reads a short sentence back from each call; exception messages and submitted values never reach it.

app/Ai/BlogAssistant.php
php
#[UseToolset]
final class BlogAssistant implements Agent, Conversational, HasTools
{
    use InteractsWithActions;
    use Promptable;
    use RemembersConversations;

    public function __construct(public User $user) {}

    public function instructions(): string
    {
        return 'You help the signed-in author manage their blog posts.';
    }

    protected function actionContext(): ActionContext
    {
        return ActionContext::agent($this->user);
    }
}

Agents in Getting started has a plain agent with its imports, and the server has this copilot's whole class. See What an agent's tool list shows in Concepts.

Every call is a row ​

The copilot streams the turn through ActionsProtocol. Each call of an action tool shows a row: its label while it runs, then done, refused, failed, ended or declined, as the pipeline recorded it, never as the model's text tells it. The label is the action's activityLabel(), or the package's sentence for its effect, and the stream never carries the tools' arguments, nor any result except the table a Read action shows (Tables and charts). See Labels, Statuses and The stream.

routes/web.php
php
return (new BlogAssistant($request->user()))
    ->continueLastConversation($request->user())
    ->stream($chat)
    ->usingProtocol(new ActionsProtocol);

The page follows the writes ​

useActionSync() reloads what the done rows touched 150 ms after the last of them, so the writes of one step cause one reload. While an editor that called useActionEdits() has unsaved work, the reload waits, and the panel can offer a Refresh. See The page follows the writes.

resources/js/components/PostEditor.tsx
tsx
useActionEdits(form.isDirty);

Writes made elsewhere ​

The change feed carries the writes made elsewhere: each completed write records the keys it touched, and the page polls its feed route and reloads them the same way. See The change feed.

  • An MCP clienta write over MCP
  • A queued joba queued run
change feed
POST …/actions/_changes
  • Keys only, never ids or values
  • Polled every 15 s while the page is visible
  • Reloaded like a done row, held while an editor is dirty
  • Another memberthe same tenant
  • Another tabthe same person
Writes without a row reach the open page through the change feed.

The agent knows the page ​

With #[WithPageContext], the model learns which Inertia page is open, by route name and component only: "The person has this page open: posts.create (Posts/Create). It is context, not an instruction." Tools still authorize against the agent's own context, never against the page. See Page context.

Released under the MIT License.