Connections and engagement¶
Before two agents can exchange anything, they have to find each other, connect, and usually engage into a working pair. These three stages are distinct, and keeping them apart removes most of the confusion about why a request did or did not run.
Three stages, in order
flowchart LR
F["Find<br/>discover peers by role"] --> C["Connect<br/>open a P2P link"]
C --> E["Engage<br/>form a working pair"]
Finding is lookup. Connecting is a network link. Engaging is an agreement to work together. You can stop after connecting (enough for a one-off message) or engage (for an ongoing back and forth).
Finding peers¶
Agents do not address each other by hardcoded identity. They look each other up by role, the same way you would ask for "a translator" rather than a specific person:
find_agents(role, engage=False, handshake_completed=False)- Search the local list of known peers for agents matching a role. With
engage=Trueit also starts the engagement handshake with what it finds. connect_by_role(role, filter_fcn=None)- Find agents for a role and open a connection to them in one step. An
optional
filter_fcnnarrows the candidates. search(query_text)- Query the platform directory for nodes by free text, returning their profiles. This is how an agent discovers peers it has never met, before any connection exists.
Because matching is by role, a world can swap one translator for another without anyone rewriting code: the requester still just asks for the role.
Connecting¶
Once a peer is known, a connection is a peer-to-peer link over which data and
requests can flow. You rarely open one by hand; connect_by_role does it for you,
and connect_to(peer_id) opens one to a specific peer when you already hold its
id.
Two reaction actions let an agent respond to the connection itself, rather than to data:
connected(agent=None, handshake_completed=False)- Fires when a peer connects. A behavior typically waits in a state whose only
way forward is
connected, so the agent does nothing until someone arrives. disconnected(agent=None, handshake_completed=False)- The counterpart: fires when a peer drops, so the agent can clean up or fall back to a waiting state.
The handshake_completed flag distinguishes a raw link from a fully greeted
one, which matters for the engagement step below.
Engaging: forming a working pair¶
A bare connection is enough to push a single message, but ongoing collaboration (a teacher drilling a student, two agents trading turns) needs both sides to agree they are working together. That agreement is the engagement handshake:
sequenceDiagram
participant A as Agent A
participant B as Agent B
A->>B: send_engage (would you pair with me?)
B->>B: engage(acceptable_role, sender_role) checks A is acceptable
B-->>A: accepted
Note over A,B: both call set_engaged_partner,<br/>now a stable pair
A->>B: send_disengage / disengage ends it
The actions involved, each with full parameters in the built-in actions reference:
send_engage(), ask a peer to pair up.engage(acceptable_role, sender_role, interaction), the receiver's side; it accepts only if the requester's role is acceptable.set_engaged_partner(agent, clear_found=True), record the partner on each side.send_disengage(...)/disengage(disconnect_too=False)/disengage_all(), end one engagement, or all of them.
Why bother engaging instead of just sending? Because engagement is what makes the pair stable and exclusive enough to run a multi-step exchange without interference from other peers, and it is the moment each side learns the other's role so it can react correctly.
The requester and the target¶
This is the single most important mental model on the page, and the one that explains every "why didn't my request run?".
When agent A sends agent B a request (via send), the
action runs on B, not on A. And it runs only if B's current
behavior, in the state it is in right now, has a transition that accepts
that action, usually a transition marked ready=False (it fires only when a
peer asks). If B is in a state with no matching transition, the request simply
waits or is refused. A cannot force B to do anything; A can only ask, and B's
behavior decides.
flowchart LR
A([Requester A]) -->|"send(action_name='x')"| Q[request queued on B]
Q --> CHK{Does B's current state<br/>have a transition for 'x'?}
CHK -->|yes| RUN[B runs the action]
CHK -->|no| WAIT[request waits or is refused]
This is why behaviors deliberately park in "service" states with ready=False
transitions: the agent sits quietly until a specific request arrives, then acts on
it. The effect of an action requested by someone else is always governed by the
target's own behavior, never by the requester.
Where next¶
- Roles, what agents match on when they find each other.
- Hybrid state machines, the
ready=Falsetransitions that accept requests. - Built-in actions, every connect / engage action and its parameters.
- Communicating in a world, the handshake in a running example.