@vendoai/agents.
agent(config)
The smallest useful composition is two keys:
Credentials
One ladder, everywhere: what you pass always wins, andVENDO_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 itsmaxToolCallsbudget. 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 arun() is the same verb plus the thread id:
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 final — read, 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.