by gintasz
Build stateful agent workflows with typed outputs, reusable tools, session forks, and ordinary TypeScript.
# Add to your Claude Code skills
git clone https://github.com/gintasz/unigentunigent is an open-source ai agents skill for AI coding assistants such as Claude Code, Codex CLI, and ChatGPT, built by gintasz. Build stateful agent workflows with typed outputs, reusable tools, session forks, and ordinary TypeScript. It has 99 GitHub stars.
unigent's catalog security scan is still queued. You can run an instant dependency and prompt-injection check now with the "Scan for vulnerabilities" button above.
Clone the repository with "git clone https://github.com/gintasz/unigent" and add it to your Claude Code skills directory (see the Installation section above).
unigent is primarily written in TypeScript. It is open-source under gintasz on GitHub, so you can review or fork the full source.
Yes. SkillsLLM lists many other AI Agents skills you can browse and compare side by side. Open the AI Agents category from the badge at the top of this page, or use the Related Skills and comparison links further down to weigh unigent against similar tools.
No comments yet. Be the first to share your thoughts!
Unlocks once the catalog security scan passes (runs nightly).
The deep catalog scan for this skill is still queued. Run an instant dependency check now instead.
Unigent requires Node.js 24 or newer.
The live trace inspector (unigent tui) additionally requires Bun.
Published packages are ESM-only.
npm install @unigent/sdk zod
npm install -g @unigent/cli
The examples below use Zod, but Unigent accepts any Standard Schema validator.
For small automations, a single Bun script is often more comfortable than adding a package.json,
lockfile, and local dependencies to a project. Start the file with #!/usr/bin/env bun; Unigent's
CLI will use Bun in both normal and TUI mode, with missing-package fallback enabled. Imports are
downloaded into Bun's global cache instead of creating project boilerplate.
Copy examples/standalone.ts into a project and launch it through Unigent:
unigent standalone.ts "Write a launch announcement"
unigent tui standalone.ts "Write a launch announcement"
The same file is directly executable when you do not need Unigent's trace transport or TUI:
chmod +x standalone.ts
./standalone.ts "Write a launch announcement"
Pi reads models and authentication from your local Pi configuration. The Claude and Codex adapters use the authenticated official CLIs installed on your machine.
👉 Your agent can invoke such workflow scripts with a bash command, isn't that cool?
import { agent, piAgent } from "@unigent/sdk";
const writer = agent({
name: "writer",
backend: piAgent(),
model: "openrouter/deepseek/deepseek-v4-flash",
});
const result = await writer.run("Write one sentence about durable software.");
console.log(result.output);
result also contains token usage, any cost reported by the harness, and the full trace. The run
starts immediately, can be aborted, and exposes its events as an async iterable while you await the
final result.
See examples/hello.ts for the
smallest complete script and
examples/pitch.ts for a workflow
using most of the API.
Every run starts with .run(prompt). The optional second argument controls what comes back: pass a
schema for a typed value or done for side-effect-only work.
import { done } from "@unigent/sdk";
import { z } from "zod";
const prose = await writer.run("Explain the tradeoff.");
const structured = await writer.run(
"Choose the strongest title.",
z.object({ title: z.string(), score: z.number().min(0).max(100) }),
);
await writer.run("Update CHANGELOG.md.", done);
done when the work ends with side effects and no prose result is useful.done is a built-in completion sentinel schema. Passing it as the
second argument makes Unigent expose the reserved unigent_return tool with an empty object input
schema. The agent is instructed to call that tool after completing the requested side effects. An
accepted call returns void and terminates the run before the agent writes a final prose response,
saving tokens that the caller would otherwise discard.
Structured output uses the same completion-tool protocol. Instead of the empty sentinel schema,
unigent_return accepts a value matching the schema passed to run().
Structured values therefore never need to be scraped from prose: Markdown, preambles, and trailing
commentary cannot corrupt the value. If the agent omits the completion call or returns an invalid
value, Unigent repairs the turn a bounded number of times and then throws
AgentRepairExhaustedError.
Pass ordinary named functions. Unigent reads their TypeScript signatures and JSDoc from the module
named by source, then builds the tool definitions for the harness.
/** Return the number of words in some copy.
*
* @promptSnippet Use this before accepting copy with a word limit.
* @promptGuideline Do not call wordCount repeatedly with unchanged text.
*/
function wordCount(text: string): number {
return text.trim().split(/\s+/u).length;
}
const editor = agent({
name: "editor",
source: import.meta.url,
backend: piAgent(),
model: "openrouter/deepseek/deepseek-v4-flash",
tools: [wordCount],
});
The opening JSDoc prose is the tool description; there is no @description tag. Both prompt tags
are optional. @promptSnippet adds a named entry to the system prompt's available-tools section.
Each @promptGuideline becomes a standalone bullet in its guidelines section, without an automatic
tool-name prefix, so every guideline must name its tool explicitly. This distinction follows Pi's
native semantics. Claude Code and Codex do not expose equivalent tool fields, so Unigent renders
both sections itself for every harness instead of appending either tag to the tool description.
Omit the tags when that extra guidance is not worth its token cost. When TypeScript source is
unavailable, the portable tool({...}) helper accepts an explicit Standard Schema input.
For compiled deployments, bake source-derived schemas after compiling the entry:
tsc && unigent bake src/worker.ts
This writes worker.unigent-tools.json beside the compiled worker.js. Production loads that
manifest without installing or loading TypeScript; development can continue reflecting directly
from a .ts entry when the optional typescript peer is installed.
Only values listed in tools are callable. Deliberate agent failure is also opt-in:
import { fail } from "@unigent/sdk";
const reviewer = agent({ ...options, tools: [fail] });
Unigent does not rename ordinary function tools: wordCount is advertised as wordCount. To the
agent, unigent_fail is another callable tool. It is special only at the TypeScript boundary:
ordinary tool errors are recoverable feedback for the agent, while unigent_fail terminates the
run and throws AgentRaisedError to the caller. Adding the fail sentinel opts the agent into that
control tool; agents cannot deliberately abort a run unless the developer enables it. User-defined
names beginning with unigent_ are rejected because that namespace belongs to protocol tools such
as unigent_return and unigent_fail.
An agent becomes a nested agent when you call it from a tool function.
const researcher = agent({
name: "researcher",
backend: piAgent(),
model: "openrouter/deepseek/deepseek-v4-pro",
});
/** Research one question and return a concise finding. */
async function research(question: string): Promise<string> {
const result = await researcher.run(question);
return result.output;
}
const author = agent({
name: "author",
source: import.meta.url,
backend: piAgent(),
model: "openrouter/deepseek/deepseek-v4-flash",
tools: [research],
});
When the author calls research, the function opens another Unigent run. The nested run keeps its
trace parentage, and its usage rolls up into the parent. The surrounding program still decides how
many agents run, which work happens in parallel, and where results go next.
Stateless runs are the default. A session keeps the harness conversation for later turns.
const review = reviewer.session();
await review.run(`
Read the repository and learn its architecture, conventions, and current behavior.
Trace the main execution paths and note the patterns that new code should follow.
Do not review or modify anything yet; reply when you have enough context.
`);
const correctnessReview = review.fork();
const maintainabilityReview = review.fork();
const [correctness, maintainability] = await Promise.all([
correctnessReview.run("Review the codebase only for correctness and edge-case failures."),
maintainabilityReview.run("Review the codebase only for maintainability and API clarity."),
]);
Both branches reuse the repository context gathered by the first turn instead of paying to rebuild it independently. All three official harnesses support forks. A session can be forked after its first completed turn, but not while a turn is active.
A scope groups related runs and owns their cumulative usage, traces, annotations, logs, cancellat