Letting agents ask for connections

Let an agent ask its user to connect the accounts it turns out to need, within limits your code sets.

You don't always know up front which accounts an agent will need. An assistant asked to book a lunch needs a calendar; asked to chase an invoice, it needs Stripe. Instead of listing every connection in its definition, let it ask: the user connects the account when the need comes up, and from then on the agent has it.

Set what it may ask for

import noclick

with noclick.as_user("user_42"):
    assistant = noclick.Agent.from_name(
        "assistant",
        create_if_missing=True,
        instructions="You help the user run their day. Ask for access to an account when you need it.",
        tools=["search"],
        can_request=["google_calendar", "gmail", "slack.send_message_to_channel"],
    )

    reply = assistant.message("Put lunch with Bo on my calendar for Friday at noon")
    print(reply.text)   # I need access to your Google Calendar first: https://noclick.com/credential/provide/...

can_request is the ceiling your code sets. Each entry is an integration (every operation), a single "integration.operation", or "*" for anything. The agent gets a request_connection tool, and when it calls it, NoClick creates a pending connection for the agent's own user, named after the service (or the name the agent chose), and hands the agent the link to share. Once the user connects, that connection's tools are the agent's from its next turn, and that turn comes on its own: the conversation that asked takes a turn (a wake-up of source answer) once the link is done, connected or expired, so the agent carries on with what it needed the account for.

The tool is offered only when the agent's own user is the one talking to it: a user's agent with that user, an account agent with your account. A user messaging a shared account agent can't grant it anything.

Grants

What the user connects is a grant. It's kept apart from the agent's definition, so updating the agent, or redeploying the code that calls Agent.from_name, never removes it:

for grant in assistant.grants():
    print(grant.connection, grant.integration, grant.status, grant.operations)
    # google_calendar  google_calendar  active  None

A grant is requested until the user connects, then active. operations is None when it covers every operation of the service, or the ones the agent asked for.

Take one back

assistant.revoke_grant(grant)

Its tools leave the agent from the next turn. The connection stays: it's the user's, and your code can still use it or delete it. Deleting a connection also removes every grant on it.

Limits

  • A request outside can_request is refused, and the agent is told what it may ask for.
  • An agent holds at most 64 tools. A request that would go over is refused and the agent is asked to name only the operations it needs, so a large integration comes in a few operations at a time.
  • Asking again while a link is still open hands back the same link instead of a second connection.
  • Your policy still decides what each granted tool may do.