A vloop exports main(resume, params). Vloopex runs it in its own Node process,
supplies the public imports, and displays its logs, chats, and child loops in
the workbench. Import from "loop-api" for the combined API, or "ai-api"
and "prompt-api" separately. The reference below comes from the checked
runtime JSDoc.
import { ai, log, getStringTagValue } from "loop-api";
export async function main() {
const chat = ai.start({ runner: "claude", prompt: "Reply with <answer>Hello</answer>." });
await chat.done();
log(getStringTagValue(chat.reply(), "answer"));
}
Run node <Vloopex-installation>/vloopex.js setup-editor . from the project.
This explicitly creates or amends .vloopex/tsconfig.json and refreshes an
ignored link at .vloopex/state/editor-runtime. Commit the config. After
copying the project or moving Vloopex, start Vloopex or run setup again to
refresh the link. Starting Vloopex does not edit the config. Setup returns
exit code 1 if it writes the config but finds settings that need attention;
it also returns 1 if setup cannot complete.
Both JavaScript and TypeScript loops receive completion, hover help, and Go to
Definition into the installed JavaScript source. Existing compiler options,
comments, path candidates, and include patterns are preserved. Setup reports
conflicting settings without replacing them. A project API override under
.vloopex/loop-runtime/ precedes the installed API. Package-specific override
types are not selected by this shared project config.
No author build step is required. .js vloops run as ESM; .ts files run with
Node's built-in type stripping. Use type annotations, interfaces, aliases, and
generics, import type for types, and explicit extensions for relative imports.
Enums, runtime namespaces, and parameter properties require code generation
and are unsupported. The editor config preserves imports and permits .ts
extensions; it also works in projects without an ESM package manifest.
import { ai, type Chat } from "loop-api";
export async function main(): Promise<string> {
const chat: Chat = ai.start({ prompt: "Say hello." });
await chat.done();
return chat.reply();
}
Use ai.start for a fresh chat on every call. Use ai.startOrResume for a chat that a resumable loop starts once. Its tag identifies a logical chat within that loop run. Re-entering the same tag subscribes to its saved chat and ignores the new launch options, including the prompt.
Use ai.startOrFollowup when later calls
should send another prompt. It starts the chat on its first call. Later calls
send the prompt after a successful turn; by default an unfinished or failed
turn is kept. onUnfinished: "queue" waits behind it; "steer" redirects it.
Later calls use turn options and retain the chat's launch settings.
Use ai.chatFromVloopexId to subscribe to
any existing Vloopex chat, including task-file and empty workbench chats.
Subscribing neither starts a turn nor takes ownership. Many loops can subscribe
at once. chat.vloopexId, the workbench Details ID, and
chat.ref()'s chatId identify the same chat.
The provider stores a separate conversation identified by
chat.providerSessionId. It is null
until known. Pass that ID as resumeSessionId when starting a chat to continue
the provider conversation. The Vloopex chat ID is not a provider session ID.
chat.followup sends another prompt. If the chat
is busy when the prompt arrives, it queues the prompt by default;
whenBusy: "steer" redirects the turn and "reject" refuses the prompt.
The same options apply to ai.followup.
Await chat.done() before reading the final
chat.reply(). It waits for the chat's result and
the turns that this loop's prompts started or queued. replyContent() includes
ordered text and media;
commandOutput() and processStderr() read process output, and transcript()
reads the complete pane text. title follows workbench renames.
The reply-content loop
starts a Codex chat with an image-generation prompt by default and saves the
returned images with caption-based filenames. Set its chatId parameter to
read an existing Vloopex chat without sending another prompt.
Use chat.saveMedia(part, file) to save a media
part, or chat.readMedia(part) to read its bytes.
Stop pauses a chat and leaves done() pending. A later prompt can complete
it. Enough accepts the current reply, including a partial reply, and
resolves the wait. Hold lets the turn finish but keeps its result pending
until Release hold or Enough; it also holds later replies and failures.
Queue retains ordered prompts. Steer uses the driver's native steering
or interrupts and replaces a turn. A turn that ends as failed or a terminal
abort rejects waits. Use incomplete() to detect an accepted partial reply.
Cache warming is enabled by default for loop-started chats. A warm-up does not
replace the loop's result or delay done(), but appears in transcript().
chat.promptCache() reports the estimate and
warmer state; the cache's lifetime remains the provider's decision.
main(resume, params) receives tracked resume state and the invocation's
JSON-compatible parameters. Mutating resume schedules an atomic checkpoint.
Before Vloopex starts a chat or a child vloop, it saves pending resume changes.
Keep state compact: the complete run record has a 10 MiB safety ceiling.
A restart enters current code at the top of main; it does not restore the
JavaScript stack. A new run gets fresh state. Interrupted loop trees recover
automatically unless the server starts with --no-auto-resume. User-stopped
loops require an explicit resume. A normal return finishes the run.
import { ai } from "loop-api";
export async function main(resume) {
resume.writer = ai.startOrResume("writer", { prompt: "Write a short plan." });
await resume.writer.done();
resume.plan = resume.writer.reply();
return resume.plan;
}
A chat or child-call handle stored in resume is encoded as a chat reference
(ChatRef) or a call reference
(VloopCallRef). Reading it reconnects the live
handle, including after a restart. Chat references can cross child-loop results;
subscribe again with ai.chatFromVloopexId(ref.chatId).
vloop.start starts a fresh child;
vloop.startOrResume binds a child by key,
defaulting to the source name. Use explicit keys for multiple logical calls to
one source. Reusing a key with different source or parameters rejects with
VLOOPEX_CHILD_LOOP_CONFLICT. A child call is
awaitable; its result is the child's JSON-compatible return value or
undefined. False, null, and undefined stay distinct.
import { vloop } from "loop-api";
export async function main(resume) {
resume.worker = vloop.startOrResume("worker-loop.js", { shard: 2 }, { key: "worker:2" });
return await resume.worker;
}
vloop.resume reconnects a call reference (VloopCallRef). Reconnecting does not independently restart a stopped child. Resume loop only leaves that child's call pending; Resume loop and stopped descendants restarts descendants first. Stop loop only lets independently tracked children and server-run chats continue. Stop loop and descendants stops the run tree. A parent does not implicitly await child calls when it returns; await the work whose result it needs.
loadPromptSnippet loads reusable Markdown
from project, user, then installation scope. A vloop:<package>/<name>
reference checks the package first, then project snippets. Legacy
.vloopex/skills/ is a migration fallback. Missing content prompts the
workbench user and is saved in the project; a default is offered, never
accepted without a user action.
promptUserForSnippet always asks. It
offers the latest numbered saved version and other versions. Changed content
is saved under an unused number rather than overwriting another snippet.
Package snippet versions live under .vloopex/prompt-snippets/vloop/<package>/.
fixssert returns a value its predicate accepts, or
asks the user to correct it and checks again. Use the value that fixssert
returns. Predicates must reject wrong types and shapes; a thrown error fails
the loop. Cancelling the dialog rejects the call.
import { fixssert, runInBash, log } from "loop-api";
export async function main() {
const count = await fixssert(0,
value => typeof value === "number" && Number.isInteger(value) && value > 0,
"Enter a positive chapter count.");
const result = await runInBash("git status --short", { check: true });
log(result.stdout);
return count;
}
runInBash runs bash -lc in the project directory.
Use setTitle, log, and
event for workbench presentation.
resolveVloopFile locates package helpers and
content. The prompt helpers parse tagged output and replace
placeholders.
Reasoning effort uses portable levels where the runner and model support them.
Vloopex translates and clamps known levels; unknown provider-specific strings
pass through. For known Kilo models with instant/thinking variants, low and
medium map to instant; high, xhigh, and ultra map to thinking. A provider may
still reject a level. The EFFORT_SUPPORT, MODEL_EFFORT_CAP, and
MODEL_EFFORT_VARIANT tables define the mapping.
Codex's gitRepoCheck controls its trusted-directory guard: on keeps it,
off skips it, auto checks the project, and autoInside checks the launch
directory.
autoInsideEvenIgnored also skips for an ignored launch directory. A failed
Git probe keeps the guard on.
Kilo model IDs use <provider>/<model>. Use kilo auth list for configured
providers and kilo models <provider> for current IDs; --verbose includes
model variant metadata. Catalogs change, so obtain IDs from your installation.