N AgentNava
AgentNava · Build

Connections and secrets

What the agent needs, who supplies it, and what happens when it is missing.

Declare what the agent needs

An agent says what it cannot work without, and says who supplies it. Declaring is part of the configuration; supplying is not.

await ws.agents.create({
  name: 'Outreach assistant',
  instructions: '...',
  connections: [{ provider: 'gmail',      scope: 'conversation', required: true }],
  secrets:     [{ name: 'APOLLO_API_KEY', scope: 'fixed',   required: true }],
});
scopeWho supplies itReaches
fixedyou, once, on the agentevery conversation, including ones already open
conversationeach conversation, on itselfthat conversation only

The two do not substitute for each other. Connecting an account on the agent does not satisfy a declaration that said conversation, and a conversation cannot supply a fixed one on its own.

Connecting an account

A connection is an account somebody signs in to, so it takes a round trip through their browser. You never see a token.

One account for everyone

const { url } = await agent.authorize('gmail');
// send them to `url`; the account attaches when they finish

await agent.connections();
// [{ provider: 'gmail', declared: true, connected: true, accountLabel: 'ops@…' }]

Each of your users connects their own

const conversation = await agent.start();

if (!conversation.ready) {
  const { url } = await conversation.authorize('gmail');
  return redirect(url);          // they sign in to their own Google
}
connected means usable right now

Not "was set up once". A revoked token flips connected to false and puts the reason in error. That is the field to show an operator, because a connection can go bad while nobody is looking.

Supplying a secret

A secret is a value you already hold, so there is no round trip and no browser. You write it and it is gone: nothing reads it back.

await agent.bind({ secrets: { APOLLO_API_KEY: '...' } });     // fixed
await conversation.bind({ secrets: { APOLLO_API_KEY: theirKey } }); // conversation

Both merge, so supplying one key leaves the others in place.

What a conversation still needs

conversation.ready;      // true when nothing REQUIRED is outstanding
conversation.requires;   // [{ kind: 'connection', name: 'gmail', required: true }]

ready is the answer most callers want. requires is the detail behind it, and the two are not the same question: a conversation can be ready and still list a requirement, because one declared required: false is something the agent works without.

A conversation is never refused

It starts even with nothing supplied, reports what is outstanding, and the agent's first reply can be the thing that asks for it. If a credential breaks mid-conversation you find out on the next turn, which is the only moment it matters.