Skip to content

Consulting

consulting is a strict 1-question-1-reply session. The initiator sends exactly one substantive envelope; the respondent sends exactly one reply; the adapter auto-closes with reason "consulting_complete".

Use it when you want a precisely-bounded query/answer exchange — exactly the shape of "ask another agent for advice and stop."

Shape#

Participants Exactly 2 (INITIATOR + RESPONDENT)
Turn order Strict: initiator first, respondent once, then closed
Auto-close Yes — after respondent's reply
Termination Auto-close, explicit close, or TTL
Default view FullTranscript()
Default expectations acks_within(30s, auto_close), reply_within(600s, auto_close)

The full transcript view (vs the windowed summary used by other adapters) reflects the use case: a consultation is short enough that the respondent should see the entire exchange unfiltered.

Lifecycle#

sequenceDiagram
    participant A as alice (initiator)
    participant H as Hub + ConsultingAdapter
    participant B as bob (respondent)

    A->>H: open(type="consulting", target="bob")
    H->>B: EV_SESSION_INVITE
    B->>H: EV_SESSION_INVITE_ACK
    H->>A: EV_SESSION_OPENED
    Note over A,B: state = ACTIVE

    A->>H: EV_TEXT (initiator_sent ← true)
    H->>B: deliver
    B->>H: EV_TEXT (respondent_replied ← true)
    H->>A: deliver
    Note over H: on_accepted → CLOSED, reason="consulting_complete"
    H-->>A: EV_SESSION_CLOSED
    H-->>B: EV_SESSION_CLOSED

The adapter rejects any further send via validate_send raising ProtocolError.

Smallest Example#

from autogen.beta import Agent
from autogen.beta.config import AnthropicConfig
from autogen.beta.knowledge import MemoryKnowledgeStore
from autogen.beta.network import (
    EV_SESSION_CLOSED,
    Hub,
    HubClient,
    LocalLink,
    Passport,
    Resume,
)

config = AnthropicConfig(model="claude-sonnet-4-6")
hub = await Hub.open(MemoryKnowledgeStore(), ttl_sweep_interval=0)
link = LocalLink(hub)

alice_hc = HubClient(link, hub=hub)
bob_hc = HubClient(link, hub=hub)

alice = await alice_hc.register(
    Agent("alice", prompt="Ask one focused question.", config=config),
    Passport(name="alice"),
    Resume(),
)
bob = await bob_hc.register(
    Agent("bob", prompt="Answer in one short sentence.", config=config),
    Passport(name="bob"),
    Resume(),
)

session = await alice.open(type="consulting", target="bob")
await session.send(
    "What's the most important property of a distributed system?",
    audience=[bob.agent_id],
)

close_env = await alice.wait_for_session_event(
    session_id=session.session_id,
    predicate=lambda e: e.event_type == EV_SESSION_CLOSED,
    timeout=60.0,
)
print(close_env.event_data["reason"])  # 'consulting_complete'

The flow:

  1. alice.open(type="consulting", target="bob") — hub posts invite to bob; bob auto-acks; session goes ACTIVE.
  2. session.send(...) — alice's first (and only) envelope.
  3. Bob's default handler probes can_send (yes — respondent hasn't replied), runs Agent.ask, sends the reply.
  4. ConsultingAdapter.on_accepted(...) sees both flags set and returns AdapterResult(next_state=CLOSED, auto_close_reason="consulting_complete").
  5. Hub posts EV_SESSION_CLOSED. alice.wait_for_session_event(...) wakes.

When to Use#

  • One-shot query/response — "ask the database expert what indexing strategy fits this query."
  • Built-in workflows where the calling code wants a single result and shouldn't wait around if no reply comes.
  • Scenarios where the audit trail benefits from each consult being a separate session id.

When NOT to Use#

  • Multi-turn back-and-forth — use conversation.
  • Multiple respondents — use discussion or workflow.
  • When the LLM may need to ask follow-up questions — consulting rejects them.

Validation Rules#

ConsultingAdapter.validate_send rejects:

  • Out-of-order sends — respondent trying to speak before the initiator's first envelope.
  • Any send after both initiator_sent and respondent_replied flags are set.

validate_send raises ProtocolError; the hub propagates it back to the sender's session.send(...) call.

State Object#

@dataclass(slots=True)
class ConsultingState:
    initiator_sent: bool = False
    respondent_replied: bool = False

Two flags. on_accepted returns CLOSED when both are true.

Auto-Close vs Explicit Close#

The two states use different close_reason values:

Trigger Reason
Adapter auto-close after reply "consulting_complete"
Explicit session.close() "explicit_close" (or whatever string you pass)
acks_within violation "expectation_violated:acks_within"
reply_within violation "expectation_violated:reply_within"
TTL expired "ttl_expired"

The reason flows on the EV_SESSION_CLOSED envelope's event_data and is also stored in SessionMetadata.close_reason for later inspection via hub.get_session(...).

For the full set of close patterns (agent-side tool, sentinel, TTL safety nets), see Closing Sessions.