Meta and Sierra Just Proposed a Sign-In Standard for AI Agents. Here’s What Your Laravel App Should Do Before the Spec Lands

How OAuth guest, read-only and write sessions work in the Personal Agent Protocol, and the scoped-token and agent-logging groundwork you can start now.


A customer messages support: “My delivery address changed and I never touched it.” The audit trail shows a PUT /account/address from the customer’s own session, with the customer’s own cookie, from the customer’s own IP. Nothing looks wrong, because nothing was wrong in any way your logs can detect. The customer had asked a browser-driving AI assistant to “check the status of my order,” and the assistant, running inside their logged-in session with every permission they hold, went further than it was asked. Your application saw a human. It had no way to see anything else, and no way to have granted the assistant less than everything.

That gap, agents acting as the user with the user’s full access and no label, is the problem a new proposal is aimed at. On October 6, Sierra announced the Personal Agent Protocol, developed with Meta and with Genesys, Instinct, Rocket, Shopify, Stripe and Walmart as partners. It’s described as an open standard for how personal AI agents authenticate with businesses and what those businesses allow them to do. The Sierra post, written by Bret Taylor and Clay Bavor, frames part of the goal as giving companies visibility into what agents are doing on their sites.

The fact that shapes everything in this post: the specification doesn’t exist publicly yet. Sierra said a v0.1 is due later in October, and in the coverage I could find as of this writing it hadn’t been published. So this isn’t a tutorial for implementing PAP. It’s an account of what’s been announced, an honest list of what hasn’t, and the groundwork a Laravel app can lay now that pays off whether this specific standard succeeds or not.


What’s Been Announced, and What Hasn’t

Announced (Sierra’s post and press coverage)Not public yet
An open standard for how personal agents authenticate with businesses and which actions they’re allowedThe specification itself
Sessions built on OAuthToken formats, claims, discovery endpoints, scope names
An agent can start as a guest, e.g. to check stock or a returns policyHow an agent proves who it represents (one analysis noted this is unspecified)
After the customer signs in, the customer chooses read-only or write accessAny production deployment by a partner
Sessions carry across channels, so a pre-login question and a post-login change belong to one visitWhether the major AI labs will adopt it; OpenAI and Anthropic were reported as not among the partners
The business chooses how agents reach it: its website, its APIs via MCP and OpenAPI, or an agent of its ownHow it coexists with Visa’s Trusted Agent Protocol, where Stripe and Shopify are also partners

Treat the right column as the important one. A standard that’s announced but unspecified can change shape completely between announcement and v0.1, and again before it’s finalized. Building to field names that don’t exist yet is how teams end up rewriting code twice.


The Model as Announced: Three Tiers

Reading the announcement as a permission ladder:

Guest. No sign-in. The agent can ask what any anonymous visitor could, such as whether something is in stock or what the returns policy says. For a Laravel app, this is your unauthenticated, public surface.

Read-only. The customer has signed in and chosen to let the agent see their account data without changing it. Order status, balances, history.

Write. The customer has signed in and explicitly allowed the agent to change things: place an order, update an address, file a claim.

Two properties of this are worth designing for regardless of the final spec. The customer sets the ceiling, not the agent and not (only) the business. And the session is meant to follow the customer across channels, so that an anonymous question and a later authenticated action are one continuous visit. How PAP will implement that continuity is one of the unpublished details, which means your app should own its own correlation identifier rather than waiting to inherit one.

The rest of this post is four pieces of groundwork, ordered from least to most effort.


Groundwork 1: Make the Guest Tier Actually Useful

The cheapest thing that helps an agent, and any scraper or future standard, is an unauthenticated, accurate, machine-readable answer to the questions agents will ask first. One analysis of the announcement put it as a question to ask yourself: could a guest agent read your stock, prices, hours and returns policy accurately without scraping your HTML?

// ❌ The agent has to scrape a product page and guess what "Only 2 left!" means
// ✅ A small, cacheable, honest endpoint
Route::get('/public/products/{sku}/availability', ProductAvailabilityController::class)
    ->middleware('throttle:agent-guest');
class ProductAvailabilityController
{
    public function __invoke(string $sku): JsonResponse
    {
        $availability = Cache::remember("availability:{$sku}", 60, fn () => [
            'sku' => $sku,
            'in_stock' => Stock::forSku($sku)->available() > 0,
            'as_of' => now()->toIso8601String(),
        ]);

        return response()->json($availability);
    }
}
// app/Providers/AppServiceProvider.php
RateLimiter::for('agent-guest', fn (Request $request) => Limit::perMinute(60)->by($request->ip()));

Two deliberate choices are in there. The response says in_stock, not an exact quantity, because a guest endpoint is also a competitor’s inventory feed. And it carries an as_of timestamp, so an agent can tell a 60-second-old answer from a live one. Guests also get their own rate limiter, separate from anything authenticated, so a misbehaving anonymous agent can’t spend your authenticated users’ budget.


Groundwork 2: Separate Read From Write at the Token, Not the Honor System

If an agent can act with the customer’s session cookie, there’s no read-only tier. There’s only “the customer.” The foundation of both tiers is a credential issued specifically to an agent, with abilities that can express “read, but never write.”

Sanctum already does this. Issue agent tokens with explicit abilities, a recognizable name, and an expiry:

// Read-only: longer-lived, can't change anything
$read = $user->createToken('agent:shopping-assistant', ['orders:read'], now()->addDays(30));

// Write: shorter-lived, issued only after explicit consent
$write = $user->createToken(
    'agent:shopping-assistant',
    ['orders:read', 'orders:write'],
    now()->addHour(),
);

Register Sanctum’s middleware aliases and enforce the ability on the route, so the tier is checked by the framework rather than remembered by each controller:

// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
    $middleware->alias([
        'abilities' => \Laravel\Sanctum\Http\Middleware\CheckAbilities::class,
        'ability' => \Laravel\Sanctum\Http\Middleware\CheckForAnyAbility::class,
    ]);
})
Route::middleware(['auth:sanctum', 'abilities:orders:read'])->group(function () {
    Route::get('/orders/{order}', [OrderController::class, 'show']);
});

Route::middleware(['auth:sanctum', 'abilities:orders:write'])->group(function () {
    Route::put('/account/address', [AccountController::class, 'updateAddress']);
});

An ability is a ceiling on top of the user’s permissions, not a replacement for them. The token authenticates as the user, so your policies still run, and a token with orders:write can still only touch that user’s orders.

Keep your abilities finer-grained than the tiers. Secondary coverage of the announcement describes the first version as offering just read-only or write. Fine, but that should be a mapping at your consent screen, not the shape of your internals. If your own abilities are orders:read, orders:write and address:write, you can present two choices to the customer today and split them later without reissuing every token.

The token-issuing endpoint must be a human-only surface. An agent that can mint or upgrade its own token has no ceiling at all. Put the consent screen behind the session guard, and never accept an agent token as proof that the customer wants to grant that agent more.

If You’re Using Laravel MCP

MCP is named in the announcement as one way a business can expose itself to agents, and Laravel’s MCP package supports OAuth through Passport. There’s a detail here that matters for the read/write split. As the Laravel MCP documentation describes the OAuth path, Mcp::oauthRoutes() advertises a single mcp:use scope, and custom scopes aren’t supported through it, because OAuth acts as a translation layer to your authenticatable model. Check the current docs, since this may change, but plan as if mcp:use carries no read/write distinction.

One structural way to get the tiers anyway is to make them separate servers, so the boundary can’t be crossed by a clever prompt:

// routes/ai.php  (a pattern, not a documented recipe)
Mcp::oauthRoutes();

Mcp::web('/mcp/orders', OrdersReadServer::class)
    ->middleware('auth:api');

Mcp::web('/mcp/orders/write', OrdersWriteServer::class)
    ->middleware(['auth:api', 'agent.write-consent']); // your middleware: checks a consent record you own

Read tools live only on the first server. The write server doesn’t merely check a flag; it doesn’t exist for a customer who hasn’t granted write.


Groundwork 3: Log Agents as Agents

The opening scenario is unanswerable today because your logs record a user, not an actor. The question you need to be able to answer is: did a person or an agent do this, with what permission, within which visit?

Laravel’s Context facade is a good fit, because anything you add is attached to log entries automatically and follows the request into queued jobs.

class RecordAgentAction
{
    public function handle(Request $request, Closure $next)
    {
        $token = $request->user()?->currentAccessToken();
        $isAgent = $token instanceof PersonalAccessToken
            && str_starts_with($token->name, 'agent:');

        Context::add([
            'actor' => $isAgent ? 'agent' : 'human',
            'agent_token_id' => $isAgent ? $token->id : null,
            // Your own correlation id: don't wait for the spec to define one
            'visit_id' => $request->header('X-Visit-Id') ?? (string) Str::uuid(),
        ]);

        return $next($request);
    }

    public function terminate(Request $request, Response $response): void
    {
        if (Context::get('actor') !== 'agent') {
            return;
        }

        AgentAction::create([
            'user_id' => $request->user()->id,
            'token_id' => Context::get('agent_token_id'),
            'visit_id' => Context::get('visit_id'),
            'method' => $request->method(),
            'path' => $request->path(),
            'status' => $response->getStatusCode(),
            'ip' => $request->ip(),
            'user_agent' => $request->userAgent(),
        ]);
    }
}

The reason the state lives in Context and not in a property on the middleware is that Laravel may resolve a fresh instance of a middleware for terminate(), so anything stashed on $this during handle() can be gone by the time the response has been sent. Writing the row in terminate() keeps the database insert off the critical path and lets you record the real status code.

Then, add the part that makes it useful in a dispute: an index on (user_id, created_at) and a retention decision. This table grows faster than almost anything else you own, and the questions you’ll ask of it are narrow, one user and one date range.


Groundwork 4: Let the Customer See and Revoke What They Granted

“The customer is always in control” is only true if the customer has a place to exercise it. That means a settings page listing connected agents, what each can do, when it last acted, and a way to cut it off.

// Human session only: an agent token must not reach these routes
Route::middleware('auth:web')->group(function () {
    Route::get('/settings/agents', fn (Request $request) => $request->user()
        ->tokens()
        ->where('name', 'like', 'agent:%')
        ->get(['id', 'name', 'abilities', 'last_used_at', 'expires_at']));

    Route::delete('/settings/agents/{id}', fn (Request $request, int $id) =>
        $request->user()->tokens()->whereKey($id)->delete());
});

Short expiries on write tokens, set at issue time as shown earlier, mean most grants end on their own. The revoke page handles the rest, and it’s also the first place you’ll find out how many agents customers are actually connecting.


What Not to Build Yet

Resist writing PAP-specific code. No hard-coded endpoint paths, no assumed claim names, no /pap/ routes. The v0.1 will define those, and the announcement alone doesn’t. The standard also has real competition and gaps: a rival protocol in the same space, partners overlapping both, and the two largest model labs reportedly outside the announced group. It may get adopted widely or it may be one of several. Everything above, though, is useful in all of those worlds, because every future standard in this space has to answer the same three questions: what can an anonymous agent see, how is a signed-in agent’s authority bounded, and how does the business know what it did.

Does an agent need to do anything for a customer on your site?
 ├─ Only anonymous things (stock, policy, hours)
 │     → Groundwork 1: public, cached, honest JSON + a guest rate limiter
 ├─ Read their account
 │     → Groundwork 2: agent-named Sanctum token, ['orders:read'], expiry
 └─ Change things for them
       → Groundwork 2: separate write ability (or separate MCP server),
         short expiry, issued only from a human consent screen

Regardless of tier:
 ├─ Log actor = agent, with token id and a visit id you own   → Groundwork 3
 └─ Give the customer a list of grants and a revoke button    → Groundwork 4

When v0.1 publishes:
 └─ Map its tiers and claims onto what you've built. Don't rebuild.

The Verdict

A sign-in standard for agents can only standardize the handshake. It can’t give your application a distinction between reading and writing it doesn’t have, a record of who acted that it never kept, or a way for a customer to take back what they granted. If those three things are missing, adopting any protocol, this one or its rivals, just produces a more official way for an agent to be indistinguishable from the customer. Build the token ceiling, the actor log and the revoke page now, in whatever shape your app needs. When the spec lands, adopting it becomes a mapping exercise instead of a rebuild, and if it never gets traction, you still have the answer to the support ticket from the top of this post.

Leave a Reply

Your email address will not be published. Required fields are marked *