Skip to content

At protocol triggers#1100

Merged
ismellike merged 35 commits intomainfrom
at-protocol-triggers
Dec 10, 2025
Merged

At protocol triggers#1100
ismellike merged 35 commits intomainfrom
at-protocol-triggers

Conversation

@ismellike
Copy link
Collaborator

@ismellike ismellike commented Dec 5, 2025

Working locally with events coming (e2e set up but with a simulated trigger)

TODO:

Copy link
Collaborator

@dakom dakom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot of at-protocol-specific knowledge that I'm unsure about (e.g. the different string formats for time, etc.), but overall the implementation looks great!

Aside for the request for moving the config var out, just a couple other high-level things:

  1. Needs an e2e test before we can merge
  2. Please pull in changes from wit repo instead of baking in here
  3. More of a question than request for changes, but we shouldn't merge until we land on an answer (raised elsewhere too): I see there's some logic for kinda dynamically filling in some data like record... maybe we should only use the sequence as the EventIdSalt, and then if they have different content it will break consensus rather than be treated as different events?

@ismellike ismellike marked this pull request as ready for review December 8, 2025 20:52
@ismellike ismellike requested a review from ueco-jb as a code owner December 8, 2025 20:52
@ismellike
Copy link
Collaborator Author

ismellike commented Dec 8, 2025

There's a lot of at-protocol-specific knowledge that I'm unsure about (e.g. the different string formats for time, etc.), but overall the implementation looks great!

Aside for the request for moving the config var out, just a couple other high-level things:

  1. Needs an e2e test before we can merge
  2. Please pull in changes from wit repo instead of baking in here
  3. More of a question than request for changes, but we shouldn't merge until we land on an answer (raised elsewhere too): I see there's some logic for kinda dynamically filling in some data like record... maybe we should only use the sequence as the EventIdSalt, and then if they have different content it will break consensus rather than be treated as different events?

1.)
I have an e2e test in already in packages/layer-tests. Did you want something else set up as well?

2.)
Got that done PR here Lay3rLabs/wavs-wasi#94

3.)
Turns out we should ignore timestamp and sequence for event id generation, and everything else should create a unique id. Done here 2d5fc45
ref timestamps not determinsitic ref sequence not deterministic

@dakom
Copy link
Collaborator

dakom commented Dec 9, 2025

I have an e2e test in already in packages/layer-tests. Did you want something else set up as well?

Not really, I missed it in simulated trigger, nice solution!

BUT - it would be better if we can treat it like our chain triggers, i.e. spin up a local server... otherwise it's not really an "end to end" test, i.e. there's no testing that it really works in the wild

I'll open a separate issue for that though, not a blocker for this PR: #1101

Copy link
Collaborator

@dakom dakom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really nice work! You covered a ton of ground, even remembering things like to remove triggers, comments, etc.

Left a bunch of comments but I think it's close to getting in :)

wanted_dids: None, // Listen to all repos
cursor: None,
compression: false,
max_message_size: self.config.jetstream_max_message_size,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this relate to the message truncation (previous comment, to 512 bytes)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is jetstream config https://github.com/bluesky-social/jetstream

maxMessageSizeBytes - The maximum size of a payload that this client would like to receive. Zero means no limit, negative values are treated as zero. (Default "0" or empty = no maximum size)

for ((collection_pattern, repo_did_filter, action_filter), lookup_ids) in
triggers_by_atproto_lock.iter()
{
// Skip if already matched by exact lookup
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait, so why are we iterating on the same collection if we're just going to exit for hits we just saw?

this could even be a performance problem if there's lots of triggers... comment isn't quite right, we're "skipping" but only skipping the more expensive pattern-match test, the intersection is itself not free

can you please change it to two completely separate collections:

  1. Exact match (strategy 1, fast path)
  2. Pattern match (like strategy 2, slower path, but will only need to iterate over the triggers in this collection)

?

You can do do this in a follow-up PR if you prefer, since it's a performance issue only at scale and will affect this file quite a bit, if you do - just open an issue to track

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated here 030bcda

StreamTriggers::AtProto { event } => {
// Convert CommitAction to AtProtoAction
let action_enum = match event.action {
streams::atproto_jetstream::CommitAction::Create => AtProtoAction::Create,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just get rid of CommitAction and use AtProtoAction everywhere?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep! included here 0151331

Copy link
Member

@ueco-jb ueco-jb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few suggestions:

  • there is no rate limiting implemented (or is it the base_delay?), I think we should be careful about that
  • reconnection logic could be refactored into a separate method for better readability

{
let stream = async_stream::stream! {
let mut reconnect_count = 0;
let max_reconnects = 10;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As David's comment, same here - this should be configurable via JetstreamConfig.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ref comment above

let delay = std::cmp::min(
base_delay * 2_u32.pow(reconnect_count),
max_delay
) + Duration::from_millis(rand::random::<u64>() % 1000);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
) + Duration::from_millis(rand::random::<u64>() % 1000);
) + Duration::from_millis(rand::thread_rng().gen_range(0..1000));

IMO more readable plus random::<u64> can generate big numbers, on the other hand my suggestion requires an extra import. 🤷

/// Set an ATProto Jetstream event trigger for a workflow
SetAtProtocol {
/// Collection NSID to filter for (e.g., "app.bsky.feed.post")
/// Supports wildcards with prefix matching (e.g., "app.bsky.feed.*")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Supports wildcards with prefix matching (e.g., "app.bsky.feed.*")
/// Supports wildcards with suffix matching (e.g., "app.bsky.feed.*")

the function matches at .*.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo correct as is, because the prefix is what's matched when using wildcard.

@ismellike ismellike requested review from dakom and ueco-jb December 10, 2025 20:29
Copy link
Collaborator

@dakom dakom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for fixing up all those comments, LGTM!

@ismellike ismellike merged commit 838b71a into main Dec 10, 2025
6 of 8 checks passed
@ismellike ismellike deleted the at-protocol-triggers branch December 10, 2025 23:49
@ueco-jb
Copy link
Member

ueco-jb commented Dec 11, 2025

I also had two suggestions in the base comment. #1100 (review)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants