Extending
July 30, 2026 · View on GitHub
Everything Iva does is a file in agent/. Drop a new one, rebuild, restart — eve picks it up. No plugin API, no registry. On the server every change ships the same way: npm run build, then iva restart (cli.md).
Adding a skill
Skills are markdown procedures in agent/skills/ that the model loads on demand. The frontmatter description is the only part the model sees before loading — write it as a trigger condition ("Use when…"), not a summary. Two shapes work: a flat <name>.md, or a <name>/ directory with a SKILL.md plus supporting files. The four bundled skills are your templates, simplest first:
- 📋 morning-digest.md — one tool call (
tasks), grouping rules, output format. Copy this for any "call a tool, format the result" job. - 🔎 web-research.md — a 4-step chain:
web_search→ pick 2–4 sources →web_fetcheach → synthesize with links. - 🌐 agent-browser/ — directory skill wrapping a CLI the model drives through
bash. - 🛡 security-defense/ — the full shape:
SKILL.md, bundled scripts, a patterns file.
⚠️ Skills go in agent/skills/ and nowhere else — never in a .claude/ directory (~/.claude/skills/, vault/.claude/skills/). That is a different tool's layout; Iva does not read it, so a skill placed there simply never loads.
If Iva should reach for your skill unprompted, name it in agent/instructions.md — that's how all four above get triggered.
MCP connections
Drop agent/connections/<name>.ts — the filename becomes the connection name. example.ts.txt in that folder is the inert template (the .txt suffix keeps eve from loading it half-configured):
import { defineMcpClientConnection } from "eve/connections";
export default defineMcpClientConnection({
url: "https://mcp.example.com/sse", // Streamable HTTP or SSE endpoint
description: "What this server does — the model reads this.",
auth: { getToken: async () => ({ token: process.env.EXAMPLE_MCP_TOKEN ?? "" }) },
// tools: { allow: ["search", "get_item"] }, // optional: restrict, add approval
});
The model discovers the server's tools through the built-in connection_search and calls them as connection__<name>__<tool>. The URL and token stay on the runtime side: keys live in .env and are never visible to the model.
Subagents
A subagent is agent/subagents/<name>/ with an agent.ts and its own instructions.md. The bundled planner is the pattern: its description tells the main agent when to delegate ("break a large goal into steps"), and a zod outputSchema forces a structured, validated reply instead of prose:
outputSchema: z.object({
goal: z.string(),
steps: z.array(z.object({
title: z.string(), detail: z.string(), priority: z.enum(["low", "med", "high"]),
})),
}),
A subagent brings its own provider and model: the planner pins Ollama Cloud (OLLAMA_API_KEY / OLLAMA_MODEL) in its agent.ts, independent of the main agent's MODEL_PROVIDER — so a cheap model for a narrow job costs nothing extra to wire.
Changing the character
Iva's voice lives in exactly one file: agent/instructions.md — tone, rules, tool preferences, hard limits. Edit it directly. It is deliberately language-neutral: the reply language comes from AGENT_LANGUAGE in .env, read at startup by agent/instructions/05-language.ts (changing it needs a rebuild + restart). The other files in agent/instructions/ are machinery, not character — 10-map.md (memory protocol), 20-core.ts (injects the vault's CORE.md), now.ts (date/time). The old rule that 05-language.ts must import only eve/instructions and process.env came from an eve 0.11.4 dev-mode bug, fixed in later Eve releases (see gotchas below) — cross-imports are safe now; the file simply has no reason to grow.
What Iva knows about you is memory, not code — that's CORE.md in the vault (memory.md).
Local development
npm ci # postinstall applies patches/eve+0.27.13.patch
npm run dev # eve dev TUI, server on http://127.0.0.1:2000
npm exec -- eve dev --no-ui --logs all # headless
The TUI is a full chat — skills, tools and subagents all work without Telegram. To smoke-test the tool loop from a script, drive the dev server with eve/client:
import { Client } from "eve/client";
const session = new Client({ host: "http://127.0.0.1:2000" }).session();
const res = await session.send("Add a task: buy coffee, high priority.");
console.log((await res.result()).message);
One gotcha — Iva runs eve 0.27.13:
- 🩹 patch-package —
patches/eve+0.27.13.patchmakes deterministic model-call errors (invalid prompt, unknown tool) fail fast instead of parking a poisoned session; upstream still classifies them as recoverable in 0.27.13. If you bump Eve, regenerate the patch (re-apply the edit tonode_modules/eve/dist/src/harness/model-call-error.js, thennpx patch-package eve) or drop it only after the targeted classification test passes against upstream.
The Eve 0.11.4 schedule crash (eve dev dying when a schedule handler imported another authored module) is fixed since 0.27.8 — verified with a probe schedule. The repo still ships no agent/schedules/*.ts: on a VPS defineSchedule never fires anyway, systemd timers do that job (deploy.md).