Build
Patterns
Build memory, scheduled turns, and multi-agent orchestration on nvoken's primitives, where the host stays in charge.
Three things come up in every evaluation: memory, scheduling, and multi-agent orchestration. nvoken does not ship any of them as a feature, and that is a choice rather than a backlog.
Each one needs product judgment nvoken does not have — what is worth remembering, when a job should run, which agent goes next. A platform that decided those for you would be a second product to keep in sync with yours. What nvoken provides instead are primitives sharp enough that each pattern is a small amount of ordinary code in your application.
Memory
nvoken stores conversations, not knowledge. There is no vector store, no memory API, and no background process reading your transcripts.
The host supplies context. Two places to put it:
In instructions, when the context is known before the turn starts:
const facts = await db.retrieveContext(customerId, question);
const agent = client.agent({
agentKey: "support",
instructions: `${basePrompt}\n\nRelevant account facts:\n${facts}`,
});
await agent.text(question, { sessionKey: `customer-${customerId}` });Building the facade per turn costs nothing — it registers nothing and makes no request. Instructions travel with each Invocation, so the retrieved context can differ every turn with no migration and no stale copy.
In a host tool, when the agent should decide whether it needs the context:
const agent = client.agent({
agentKey: "support",
tools: [
{
mode: "host",
name: "search_knowledge_base",
description: "Search internal docs for an answer.",
inputSchema: {
type: "object",
properties: { query: { type: "string" } },
required: ["query"],
additionalProperties: false,
},
handler: ({ query }) => kb.search(query as string),
},
],
});The retrieval runs in your application, against your data, under your access rules. nvoken records the call and its result in the transcript, so a support question about why the agent said something has an answer.
For memory that persists across conversations, write it in your own database at the end of a turn and retrieve it at the start of the next. The Session holds the conversation; your product holds what it learned.
Scheduled and recurring turns
You already have the two pieces this needs: a scheduler and a required idempotency key.
// Runs every morning from your own cron.
async function dailyDigest(accountId: string, day: string) {
await agent.invoke("Summarize yesterday's activity.", {
sessionKey: `digest-${accountId}`,
idempotencyKey: `digest:${accountId}:${day}`,
});
}Because idempotency_key is required and nvoken compares the whole request, a
cron that fires twice, a queue that redelivers, or a retry after an ambiguous
timeout all produce one turn. The second attempt returns the original with
deduplicated: true. Scheduled work is retry-safe without a lock table.
Two details make this durable in practice:
- Use
invoke()rather thantext(). Admission returns immediately, and a worker reads the result later. The turn does not depend on your cron process staying alive. - Derive the idempotency key from the schedule slot, not from the clock at send
time.
digest:acct-12:2026-08-08is stable across retries;digest:acct-12:${Date.now()}is not.
Set a webhook target if you want to be told when the turn settles instead of
polling for it.
Multi-agent orchestration
Your application is the orchestrator. An "agent" is an agent_key plus the
instructions and tools you send with a turn, so running several is just making
several Invocations.
In parallel, when the work is independent — use separate Sessions:
const [research, pricing] = await Promise.all([
researcher.text(question, { sessionKey: `${jobId}-research` }),
analyst.text(question, { sessionKey: `${jobId}-pricing` }),
]);
const answer = await editor.text(
`Combine these findings:\n\n${research}\n\n${pricing}`,
{ sessionKey: `${jobId}-final` },
);In sequence within one conversation, when later turns should see what earlier ones did — reuse the Session and change the definition per turn. Each Invocation carries its own instructions, model, and tools, so a cheap triage model and an expensive reasoning model can take turns in the same thread.
The primitives that make this safe:
- One turn at a time per Session. A second concurrent admission is rejected
with
session_invocation_active. You cannot accidentally interleave two agents' output in one conversation. if_activewhen you deliberately want to replace live work:"interrupt"stops the current turn and keeps what it produced before admitting the replacement;"supersede"discards it.agent_keyas the filter. Every Invocation records which agent ran, so usage, transcripts, and dashboards can be read per agent.
What nvoken will not do is decide which agent runs next, pass control between them, or enforce a handoff protocol. That logic belongs where the rest of your product's rules live.
Where this boundary comes from
The same reasoning runs through all three: nvoken stores what your agent did, never what it does. Adding memory policy, schedules, or an orchestration graph would mean storing behavior — and a stored copy of your product's behavior is a copy that drifts.
What nvoken supports is the full list of what is and is not in the box.