Vloop API
    Preparing search index...

    Vloop API

    Writing vloops

    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.