AT Intents proposal

I work on Skyreader and have recently been trying to improve the cross-app sharing experience (e.g. “send this article to semble/margin/etc”). The problem I’m running into is each app integration requires a bespoke implementation and the “save to” UX starts to get cluttered as the number of integrations increases. What would be nice is if the atproto ecosystem had a standard and generic way for cross-app sharing that was usage aware so you could filter by apps you actually used.

So I’ve put together a AT Intents proposal that tries to do that. AT Intents would be a standard, registry-free way for atproto apps to declare what they can do so that other apps can discover and route users to them.

As the name implies, it’s basically app/android/web intents but for atproto.

The basic idea is to have apps that provide some service (e.g. save url) write a dev.at-intent.capability record in the app’s repo that describes what they can do as a (subject, verb, handler) triple along with a delivery method (lexicon write or XRPC call). Then when a user first logs into that app it writes a dev.at-intent.usage record in the user’s repo with a reference to the app’s capability record.

With those two records a consumer app (e.g. a bookmarking extension) can scan the user’s repo to find all the atproto apps they actually use and surface just those.

I’ve put together a tangled repo with more details/lexicons/etc. along with a “share-sheet” demo bookmarklet that shows how a consumer might use it (scans the user’s repo for usage records and shows all the matched capabilities that can do something with the current page).

I’ve also prototyped an implementation in Skyreader so if you try out the demo bookmarklet it should just work to save an article or subscribe to a feed in Skyreader.

Does this seem like a reasonable approach? Is there some prior art that I missed? What do you all think?

11 Likes

I believe the app capability side of this overlaps with the Community App Lexicon; I think there’s value in adding a usage record type to the published community.lexicon.app namespace.

2 Likes

ah nice I hadn’t noticed community.lexicon.app.*. yeah assuming people agree with the concept of Intents it probably should live as extensions and/or new records under community.lexicon.app.*

2 Likes

Great thread to kick off @disnetdev.com - thank you!

I think intents can be in that namespace but right now anyone can make app entries - the “how to tell official app claimed entry” is the part that needs figuring out.

so maybe community.lexicon.app.intents as the namespace and the app authority comes from a similar process as lexicon publishing?

the “how to tell official app claimed entry” is the part that needs figuring out.

I think you can kind of sidestep the question with this design. the intents.usage record in the user’s repo connects the intents.capability record in the app’s repo so no need to figure out “official” entry. Just follow links.

In my prototype I’m using bare dids in the usage record:

{
  "app": "did:plc:ra4jsemddo2ii4pn5jaf6x4v", // did of @skyreader.app
}

but could also be at links directly to the capability record(s)

oh interesting!

Yeah, I was more thinking where to fetch them from. That is, for Semble (as an example), where does it publish the “can save a link” app capability?

Still comes down to bootstrapping from a registry? But “advanced” allows to put any did or aturi so I can make my own app?

In which case, having ATStore.fyi help with the community registry could be a thing.

Ah I really think we can entirely avoid a centralized registry. Or rather a centralized registry solves an orthogonal problem of app discovery, not app usage/capability discovery if that makes sense.

Let me use a concrete example from my skyreader prototype.

To implement intents for Skyreader I did a one-time publish of four dev.at-intent.capability records to the @skyreader.app PDS:

e.g. the save to skyreader capability looks like:

{
  "icon": "https://skyreader.app/icons/icon-512.png",
  "name": "Save to Skyreader",
  "verb": "save",
  "$type": "dev.at-intent.capability",
  "input": [
    {
      "kind": "string",
      "name": "title",
      "required": false
    }
  ],
  "subject": [
    {
      "kind": "uri"
    }
  ],
  "delivery": "service",
  "endpoint": "https://api.skyreader.app/xrpc/app.skyreader.feed.save",
  "serviceId": "skyreader_api",
  "description": "Save an article to your Skyreader reading list (read-it-later). Accepts any web URL. Not for following a source over time; use subscribe for that."
}

Then I updated the skyreader appview to write a dev.at-intent.usage in the user’s PDS when they first login to skyreader that links to the app’s did. Here’s that record for my @disnetdev.com account: PDSls

{
  "app": "did:plc:ra4jsemddo2ii4pn5jaf6x4v",
  "$type": "dev.at-intent.usage",
  "lastSeenAt": "2026-06-30T20:16:58.071Z"
}

Now when some other app want’s to implement a share sheet extension it can just list all the usage records in the current user’s PDS and then fetch the linked capabilities from each app’s PDS and dynamically build the share sheet with exactly the apps the user is currently using.

No centralized registry or bootstrapping is required. Apps can just register their capabilities by publishing to their own repo and then users get linked by just using the apps.

Writing the capability records should be pretty straightforward for app authors to do on their own but atstore.fyi could also have an owner section with a wizard/validator or something.

I mentioned this elsewhere, but this FEP from the Fediverse/ActivityPub community might be of relevance: FEP-3b86 Activity Intents

1 Like

Awesome, thanks for the extended example.

Note: I think you mean Skyreader account repo not PDS above.

Don’t need to insert atstore in the middle if not needed!

Community.lexicon.capability/intent could be a base lexicon, but not important at this point.

1 Like