Skip to content

Conversation

@devin-ai-integration
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot commented Oct 5, 2025

  • Fixes HYBRID-59

Summary

Adds two new filter methods for message filtering by sender identity:

  • filter.isFromSelf() - Checks if message is from the agent itself

- Add isFromSelf() method to FilterAPI as alias for fromSelf()
- Add async isFrom(address) method to filter by Ethereum address
- Update filterMessages to support both sync and async filter callbacks
- Implement address resolution using AddressResolver with caching
- Add comprehensive test coverage for both new methods
- Export AddressResolver from @hybrd/xmtp package
- Update README with new filter methods and async usage example
@linear
Copy link

linear bot commented Oct 5, 2025

@devin-ai-integration
Copy link
Contributor Author

Original prompt from Ian
Hey Devin,

I need you to implement two new filter methods for the Hybrid framework's message filtering system. This is for Linear ticket **HYBRID-57**.

## Task Overview

Add `filter.isFromSelf()` and `filter.isFrom(address: `0x${string}`)` methods to enable message filtering by sender identity. These should integrate into the existing `FilterAPI` interface used by the `filterMessages` behavior.

## Current State

The filtering system is in `packages/core/src/behaviors/filter-messages.ts` with an existing `FilterAPI` interface. There's already a `fromSelf()` method that checks if a message is from the agent itself. The system has access to:
- `context.message.senderInboxId` - XMTP inbox identifier  
- `context.runtime.sender.address` - Ethereum address (when available)
- `AddressResolver` class for inbox ID ↔ address conversion with caching

## Implementation Requirements

### 1. Extend FilterAPI Interface

Add these methods to the `FilterAPI` interface in `packages/core/src/behaviors/filter-messages.ts`:
```typescript
isFromSelf(): boolean // Need to resolve naming conflict with existing fromSelf()
isFrom(address: `0x${string}`): boolean

2. Implement the Methods

In the filterAPI wrapper implementation within the same file:

For isFrom(address):

  • Compare the provided address with the message sender's address
  • Use context.runtime?.sender?.address when available
  • Fall back to resolving from context.message.senderInboxId if needed
  • Normalize addresses to lowercase for case-insensitive comparison
  • Handle the async nature of address resolution if required

For isFromSelf():

  • Resolve the naming conflict with existing fromSelf() method
  • Decide whether this should be an alias or replacement
  • Ensure consistent behavior

3. Design Decisions to Make

You'll need to decide on:

  • Address resolution strategy (runtime context vs AddressResolver)
  • Error handling for failed address resolution
  • Method naming resolution for isFromSelf() v... (2121 chars truncated...)
</details>

@devin-ai-integration
Copy link
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Oct 5, 2025

Deploying hybrid with  Cloudflare Pages  Cloudflare Pages

Latest commit: d5755cd
Status: ✅  Deploy successful!
Preview URL: https://8b3dfb95.hybrid-2zz.pages.dev
Branch Preview URL: https://devin-hybrid-57-1759706919.hybrid-2zz.pages.dev

View logs

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 4 files

Prompt for AI agents (all 1 issues)

Understand the root cause of the following 1 issues and fix them.


<file name="packages/core/src/behaviors/filter-messages.ts">

<violation number="1" location="packages/core/src/behaviors/filter-messages.ts:104">
A new `AddressResolver` instance is created on every `isFrom()` call. Because the resolver&#39;s cache is instance-based, this pattern defeats the caching mechanism, causing significant performance degradation by making expensive address resolution calls for every message instead of using the cache.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.

context.message.senderInboxId === context.client.inboxId,
isFrom: async (address: `0x${string}`) => {
const normalizedAddress = address.toLowerCase()
const resolver = new AddressResolver(context.client)
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 5, 2025

Choose a reason for hiding this comment

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

A new AddressResolver instance is created on every isFrom() call. Because the resolver's cache is instance-based, this pattern defeats the caching mechanism, causing significant performance degradation by making expensive address resolution calls for every message instead of using the cache.

Prompt for AI agents
Address the following comment on packages/core/src/behaviors/filter-messages.ts at line 104:

<comment>A new `AddressResolver` instance is created on every `isFrom()` call. Because the resolver&#39;s cache is instance-based, this pattern defeats the caching mechanism, causing significant performance degradation by making expensive address resolution calls for every message instead of using the cache.</comment>

<file context>
@@ -94,11 +96,39 @@ export function filterMessages(
+					context.message.senderInboxId === context.client.inboxId,
+				isFrom: async (address: `0x${string}`) =&gt; {
+					const normalizedAddress = address.toLowerCase()
+					const resolver = new AddressResolver(context.client)
+
+					try {
</file context>

✅ Addressed in 02bf5c6

Move AddressResolver instantiation outside filterAPI to enable cache reuse
across multiple isFrom() filter calls within the same context, improving
performance as recommended by code review.
)

// Create AddressResolver instance for reuse across multiple isFrom() calls
const resolver = new AddressResolver(context.client)
Copy link
Owner

Choose a reason for hiding this comment

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

Wait, why do we need to resolve an address when its a 0xstring? Address resolver is only for ENS names

@devin-ai-integration
Copy link
Contributor Author

You're right to question this! Let me clarify what's happening here:

The Problem

  • Input: isFrom(address) receives an Ethereum address (0x${string})
  • Message data: Only has context.message.senderInboxId (XMTP inbox ID, not an address)
  • Need: Compare the two to filter messages

What AddressResolver Does Here

The AddressResolver is NOT being used for ENS resolution in this case. Instead, it's resolving XMTP inbox IDs to Ethereum addresses:

// We have: message.senderInboxId (e.g., "inbox-abc123")
// We need: sender's Ethereum address (e.g., "0x1234...")
const senderAddress = await resolver.resolveAddress(
    context.message.senderInboxId,  // XMTP inbox ID
    context.conversation.id
)
// Now compare: senderAddress === inputAddress

Looking at the AddressResolver class in packages/xmtp/src/resolver/address-resolver.ts, it's designed to resolve addresses from conversation members and inbox state - this is an XMTP protocol concept where inbox IDs map to addresses.

Alternative Approaches

If there's a better/simpler way to get the sender's address directly from the message or context without using AddressResolver, I'd be happy to refactor! For example:

  • Is there a context.message.senderAddress field I missed?
  • Is there a different utility for inbox ID → address conversion?

Let me know if you'd prefer a different approach!

@ian
Copy link
Owner

ian commented Oct 6, 2025

I figure we should tackle these two tickets at the same time: https://linear.app/01studio/issue/HYBRID-59/add-sender-and-subjects-back-to-runtime

Can you add sender and subjects back to the runtime? This should make it so we don't have to resolve sender for filterMessages only, we can do this higher up the chain and not have to check multiple times.

- Uncommented sender and subjects fields in AgentRuntime interface
- Added resolveSender() helper function in XMTP plugin to resolve sender addresses
- Integrated address resolution using AddressResolver and BasenameResolver
- Added subjects extraction from message content using extractSubjects()
- Updated all 4 message handler locations to populate runtime.sender and runtime.subjects
- Simplified isFrom() filter to use runtime.sender.address directly
- Updated tests to mock runtime.sender and runtime.subjects
- Removed redundant AddressResolver instantiation from filter-messages behavior

This resolves the performance concern by resolving addresses once at
the runtime level rather than repeatedly in filter methods.

Fixes HYBRID-59
Relates to HYBRID-57
Resolved merge conflict in packages/xmtp/src/plugin.ts by accepting
origin/main's restructured event handler architecture which already
includes the HYBRID-59 sender/subjects resolution logic.
@devin-ai-integration
Copy link
Contributor Author

Great question! The current implementation (as of commit cd618ea) does not use AddressResolver for the isFrom() filter anymore.

Here's what changed:

Old Implementation (what you commented on)

// Created AddressResolver instance for each filter check
const resolver = new AddressResolver(context.client)

isFrom: async (address: `0x${string}`) => {
  const senderAddress = await resolver.resolveAddress(
    context.message.senderInboxId,  // ❌ Resolving inbox ID to address
    context.conversation.id
  )
  return senderAddress?.toLowerCase() === normalizedAddress
}

New Implementation (HYBRID-59)

// No AddressResolver in filter-messages.ts at all!

isFrom: async (address: `0x${string}`) => {
  const normalizedAddress = address.toLowerCase()
  const senderAddress = context.runtime.sender?.address?.toLowerCase()
  
  return senderAddress === normalizedAddress  // ✅ Direct comparison
}

What Happened?

Your feedback prompted HYBRID-59, which added sender and subjects to the runtime context. Now:

  1. Address resolution happens once in the XMTP plugin (packages/xmtp/src/plugin.ts) using the resolveSender() helper
  2. runtime.sender.address is populated for every message at the plugin level
  3. isFrom() just compares the pre-resolved address with the input address

This eliminates the redundant AddressResolver calls and addresses exactly what you requested! The AddressResolver is now only used at the plugin level, not in filters.

Summary: The isFrom() method receives a 0x${string} as input and compares it with runtime.sender.address (which was already resolved). No ENS resolution or AddressResolver involved in the filter logic anymore.

@linear
Copy link

linear bot commented Oct 6, 2025

* @param client - XMTP client instance
* @returns XmtpSender object with address, inboxId, name, and optional basename
*/
async function resolveSender(
Copy link
Owner

Choose a reason for hiding this comment

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

Just use the Resolver router top level class. It exposes helper functions for each of the resolver. Don't use individual resolvers.

}
]

const basenameResolver = new BasenameResolver({
Copy link
Owner

Choose a reason for hiding this comment

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

Same here, use top level resolver router.

devin-ai-integration bot and others added 4 commits October 6, 2025 01:55
…lvers

- Replace individual resolver imports with top-level Resolver class
- Remove custom resolveSender() function, use Resolver.createXmtpSender()
- Update extractSubjects() to accept Resolver instance
- Use Resolver.resolveName() for universal name resolution
- Create single Resolver instance shared across all event handlers
- Fix test mock to include required senderInboxId field

Addresses code review feedback from Ian on PR #88
@ian ian changed the title feat(core): Add isFromSelf() and isFrom() filter methods feat(core): Reintroduce sender + subjects Oct 6, 2025
@linear
Copy link

linear bot commented Oct 6, 2025

@ian ian changed the title feat(core): Reintroduce sender + subjects Add isFromSelf and isFrom(address) filters Oct 6, 2025
@ian ian changed the title Add isFromSelf and isFrom(address) filters Add isFromSelf filter Oct 6, 2025
@ian ian merged commit 82e5f13 into main Oct 6, 2025
12 checks passed
@ian ian deleted the devin/HYBRID-57-1759706919 branch October 6, 2025 07:58
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.

2 participants