name: authoring-magenta-scripts description: How to author magenta scripts. Magenta scripts allow for programmatic orchestration of magenta threads. Use this skill when the user explicitly asks you to write a magenta script (not just a regular script).
Discovery and the registerScript contract
Each script package lives in its own subdirectory of a scripts directory
(.magenta/scripts/<package>/ or ~/.magenta/scripts/<package>/), so multiple
independent installations can coexist. A package is a directory containing an
index.ts entry point.
Magenta discovers scripts by forking each <package>/index.ts file. That file
is executed, and all registerScript() calls report script definitions to
magenta via IPC.
The magenta-sdk symlink
Scripts import the SDK from magenta-sdk, a symlink inside each package
directory (e.g. .magenta/scripts/my-package/magenta-sdk) pointing at
magenta.nvim/sdk in the plugin installation. Scripts import it via
./magenta-sdk/index.ts (and ./magenta-sdk/testing.ts for the test harness).
Magenta does not create or manage this symlink for you. When authoring a script package you (the agent or the user) must create it yourself, e.g.:
ln -s <plugin>/sdk .magenta/scripts/my-package/magenta-sdk
The directory layout looks like:
.magenta/scripts/
my-package/
magenta-sdk -> <plugin>/sdk
index.ts
my-script.ts
other-package/
magenta-sdk -> <plugin>/sdk
index.ts
registerScript(name, description, parameterSchema, runner);
name: unique script name (how agents invoke it).description: shown to agents so they know when to use it.parameterSchema: a JSON Schema describing theparametersthe runner receives.runner(parameters, thread, log): an async function that does the work.
The runner receives:
parameters: the validated input object.thread(prompt, yieldSchema, options?): spawns a real magenta thread seeded withprompt, equipped with ayield_to_parenttool whose input schema isyieldSchema. Resolves to the structured value the agent yields. Use the generic to type the result:await thread<{ done: boolean }>(...).optionsmay include:contextFiles: absolute paths to seed into the thread's context.systemReminder: a recurring system reminder injected into the thread.cwd,profile,model,tools.
log(message): reports progress; surfaced in the magenta Scripts section.
Minimal end-to-end example
// index.ts. file
import { registerScript } from "./magenta-sdk/index.ts";
registerScript(
"summarize-file",
"Summarizes a file and returns a one-line summary",
{
type: "object",
properties: { path: { type: "string" } },
required: ["path"],
},
async (params: { path: string }, thread, log) => {
log(`summarizing ${params.path}`);
const result = await thread<{ summary: string }>(
`Read ${params.path} and summarize it in one sentence.`,
{
type: "object",
properties: { summary: { type: "string" } },
required: ["summary"],
},
);
log(`summary: ${result.summary}`);
},
);
Testing a script
Use the in-process test harness from ./magenta-sdk/testing.ts. It drives your
runner with test-double thread/log — no subprocess and no magenta required.
Statically import your script module first so its registerScript call runs.
import { expect, it } from "vitest";
import { runScript } from "./magenta-sdk/testing.ts";
import "./summarize-file.ts"; // registers the script
it("summarizes", async () => {
const { handle, donePromise } = runScript("summarize-file", {
path: "README.md",
});
const call = await handle.nextThread();
expect(call.prompt).toContain("README.md");
call.yield({ summary: "It is a readme." });
await donePromise;
expect(handle.logs).toContain("summary: It is a readme.");
});
handle.nextThread() resolves with each pending thread() call (in order),
exposing prompt, yieldSchema, options, and yield(value) / reject(error)
to settle that specific call. handle.logs captures log() output.