Skip to main content
Everything on this page is exported from @vendoai/agents.

agent(config)

The smallest useful composition is two keys:

Credentials

One ladder, everywhere: what you pass always wins, and VENDO_API_KEY fills only the slots you left unset. There are no key-conditional branches and no capability checks — a key problem surfaces on the first real service call.

VendoAgent

door is present exactly when the harness thinks outside this process (requires.toolDoor). Mount it at DOOR_PATH (/api/vendo/mcp).

respond

One turn for a waiting person. Returns an AI-SDK UI-message-stream Response with x-vendo-thread-id set.
It is session(subject, options) plus stream(message) collapsed into one call, with the header stamped. session() is unchanged and still available for a host that wants the session object — and session.stream() now stamps the same header.

run

One non-interactive run.

AgentRun

threadId is readable immediately, before the first event. Awaiting the run gives the report; iterating events does not consume it.

RunEvent

AgentReport

  • status"stopped" covers an aborted run and one that spent its maxToolCalls budget. Both still return a full report.
  • summary — the model’s own closing account, never a sentence of Vendo’s.
  • refs.approvals — the ids of approvals this run parked. See below.
  • usage{ inputTokens, outputTokens, cacheReadTokens?, cacheWriteTokens?, model? }. This is what you meter on.

Approvals inside run

An ask inside a non-interactive run() parks the approval durably, and the tool call returns denied with the approval’s id. The run continues with that answer and finishes normally. There is no resume. report.refs.approvals is the list of ids to put in front of a human; once someone has decided, re-entry is a fresh run(). The interactive lane is unchanged: a turn driven by respond() waits about 90 seconds for the person to decide, and an approved call re-dispatches byte-for-byte.

Follow-ups

The follow-up to a run() is the same verb plus the thread id:
Never respond() — that lane opens a turn that will wait on a human who is not there.

session

Unchanged. session(subject, options) returns an AgentSession with a threadId, a stream(message, options) that returns a UI-message-stream Response (now carrying x-vendo-thread-id too), and on("approval", handler) for present-user decisions. SessionOptions is { user?, context?, headers?, threadId? }.

Tools

api() serves .vendo/tools.json with layered overrides, present-header forwarding behind the origin gate, and actAs for away runs ({ dir?, actAs?, baseUrl?, untrustedOriginPolicy?, fetch? }). tool() takes { name, description?, risk?, inputSchema, execute }. risk is your call and it is finalread, write, or destructive. Omit it and the tool is ungraded, which the guard treats like destructive and asks about, which unattended means denied.

Adapters

postgres(url, { blobs?, encryption?, allowUnencryptedSecrets? }) — the blob adapter rides beside the store it was configured with. e2b({ apiKey?, template?, timeoutMs? }) reads E2B_API_KEY as its credential; the harness’s own template always wins over the adapter’s.

Constants

Tools and safety

Risk grading, guard.bind, approvals, and away-run scopes.

How Vendo works

Where @vendoai/agents sits among the blocks.