Built by 4onStudios · Issues · Contribute
Iris Agent is the standalone coding-agent service used by AIRIS.
It provides streaming chat, workspace tools, LSP routes, MCP integration, command approvals, and run lifecycle APIs. It can run as:
- HTTP Service - RESTful API under
/api/agent - CLI - Interactive chat in the terminal
- ACP Server - Agent Client Protocol via stdio for seamless IDE integration
The SDK and ACP server require Node.js >=22.13.0. IrisClient is a
Node.js API for IDE desktop or backend processes; it is not intended to run in
a browser renderer.
This project can be installed and run with either npm or Yarn. Configure your provider API key (such as OPENROUTER_API_KEY or OPENAI_API_KEY). By default, Iris Agent routes through OpenRouter (openrouter/openai/gpt-5.3-codex) when OPENROUTER_API_KEY is provided or configured.
# npm
npm install
OPENROUTER_API_KEY=... npm start
# yarn
yarn install
yarn startnpm install
OPENROUTER_API_KEY=... npm startThe service listens on port 8080 by default. Set PORT to change it. GET /health reports service readiness.
To run the standalone agent service for an IDE integration:
git clone https://github.com/4onstudios/iris-agent.git
cd iris-agent
npm install
npm startThe service listens on port 8080 by default and exposes its API under
/api/agent. Set PORT to use another port and configure
AGENT_ALLOWED_ORIGINS with the IDE's origin when browser CORS is required:
PORT=8080 AGENT_ALLOWED_ORIGINS=http://localhost:3000 npm startThe HTTP API is mounted below /api/agent. The main endpoints are:
| Endpoint | Purpose |
|---|---|
POST /api/agent/chat |
Send a message and optional workspace, model, history, tools, skills, MCP, and approval settings. |
GET /api/agent/runs/:runId |
Read the current run lifecycle snapshot. |
GET /api/agent/runs/:runId/events |
Read persisted run events. Supports afterSequence and limit (1-500). |
POST /api/agent/runs/:runId/cancel |
Request cancellation of a run. |
POST /api/agent/command-confirmation |
Approve or skip a pending command execution. |
GET /api/agent/skills |
List discovered skills. |
GET /api/agent/tools |
List native and MCP tools with their input schemas. |
GET /api/agent/slash-commands |
List enabled slash commands. |
POST /api/agent/mcp/inspect |
Inspect the tools exposed by one MCP server. |
POST /api/agent/mcp/call |
Invoke an MCP tool. |
The service also exposes file, chat-session, semantic-search, and LSP routes under
/api/agent. Those routes are intended for the AIRIS desktop client and are
implemented in api/; use GET /api/agent/tools and
GET /api/agent/skills for runtime discovery.
Example chat request:
curl -X POST http://localhost:8080/api/agent/chat \
-H 'Content-Type: application/json' \
-d '{"message":"Explain this project","workspaceRoot":"/path/to/project"}'Run lifecycle states and event payloads are returned by the run endpoints. Store
the returned runId from a chat response if the client needs polling,
progress-event retrieval, or cancellation.
Persisted tool_call and tool_result events carry the redacted action
details needed to render a replayable tool timeline: name (and the deprecated
compatibility alias toolName), args, toolCallId, and status. Completed
actions also include result when its JSON representation is at most 16 KiB.
Larger or non-JSON-representable results are replaced with a bounded summary:
{ truncated: true, reason?: string, originalByteLength?: number, preview?: string }.
For oversized object results, scalar metadata such as success, status,
error, exitCode, paths, and search counts may also be retained with long
strings shortened. Legacy stored action events are normalized when read so both
tool-name labels remain available. This lets clients show, for example, the
path and line range read or the search query, matched files, and result counts
without relying on the live stream.
# Interactive chat with default model (openrouter/openai/gpt-5.3-codex)
OPENROUTER_API_KEY=... npm run cli -- --workspace /path/to/project --chat
# Interactive chat with a specific model
OPENROUTER_API_KEY=... npm run cli -- --workspace /path/to/project --chat --modelId openrouter/anthropic/claude-3.7-sonnetThis starts an interactive streaming chat session in your terminal with access to the workspace and tools.
OPENROUTER_API_KEY=... npm run cli -- --workspace /path/to/project --acpThis starts an ACP (Agent Client Protocol) server over stdio, allowing IDE
integrations and other ACP clients to communicate with the agent. Models can be configured at startup or dynamically per-session / per-prompt. Standard
output is reserved for newline-delimited JSON-RPC messages; logs are written to
standard error. Each ACP process is bound to the workspace supplied at startup.
To switch workspaces, close the process and respawn iris-agent with the new
--workspace path.
When the current directory is the target workspace, --workspace is optional:
OPENROUTER_API_KEY=... npx @4onstudios/iris-agent@latest --acpIf the selected model's credentials are missing, Iris Agent stops before opening
the ACP connection and prints the required environment variable, a terminal
command, and a ready-to-paste VS Code ACP Client configuration. Configure API
keys in acp.agents.<name>.env; do not put them in the argument list:
{
"acp.agents": {
"Iris Agent": {
"command": "npx",
"args": ["@4onstudios/iris-agent@latest", "--acp"],
"env": {
"OPENROUTER_API_KEY": "your-openrouter-api-key"
}
}
}
}Use --modelId to select a direct provider: openai/gpt-4o with
OPENAI_API_KEY, anthropic/claude-sonnet-4-5 with ANTHROPIC_API_KEY,
google/gemini-2.5-pro with GOOGLE_GENERATIVE_AI_API_KEY, or
huggingface/... with HF_TOKEN. Local ollama/<model> sessions do not need
a cloud API key.
Iris Agent advertises ACP session loading and listing, so VS Code ACP Client can display and restore persisted conversations for the current workspace. It also returns the per-session model configuration option required by the extension's composer controls.
iris-agent [--workspace <path>] [--acp | --chat] [--modelId <model>]On macOS or Linux, install the CLI through the Iris Agent Homebrew tap:
brew tap 4onstudios/iris-agent
brew install iris-agentThen start an interactive session or an ACP server:
iris-agent --chat
OPENROUTER_API_KEY=... iris-agent --acpThe Homebrew formula installs Node.js 22 and keeps Iris Agent and its dependencies under Homebrew's managed prefix. Upgrade it with:
brew update
brew upgrade iris-agentThe tap is updated automatically by GitHub Actions when a newer version is
published to npm. The updater runs daily and can also be started manually from
the repository's Actions tab. It requires a repository secret named
HOMEBREW_TAP_TOKEN with permission to push to
4onstudios/homebrew-iris-agent.
Options:
--workspace(-w) - Path to the workspace/project root; defaults to the current working directory--acp(-a) - Start ACP protocol server (stdio-based)--chat(-c) - Start interactive chat mode--modelId- Model identifier used for chat/ACP sessions (default:openrouter/openai/gpt-5.3-codexorMODEL_ID/OPENROUTER_MODELenv vars)
Running the CLI without --chat or --acp prints help.
Examples:
# Interactive chat
npm run cli -- --workspace . --chat
# Interactive chat with custom model
npm run cli -- --workspace . --chat --modelId openrouter/anthropic/claude-3.7-sonnet
# ACP server for IDE integration
npm run cli -- --workspace . --acp
# ACP server from the current workspace
npx @4onstudios/iris-agent@latest --acp
# ACP server with custom default model
npm run cli -- --workspace . --acp --modelId openrouter/openai/gpt-4o
# HTTP service (default)
npm startConfigure the precise browser origins permitted to call this service:
AGENT_ALLOWED_ORIGINS=https://airis.4onstudios.com npm startMultiple origins may be supplied as a comma-separated list. Requests without an Origin header (such as a local CLI or reverse proxy) are accepted; browser origins are denied unless explicitly configured.
Configure the API key for the model provider selected by the client:
OPENAI_API_KEYANTHROPIC_API_KEYGOOGLE_GENERATIVE_AI_API_KEYOPENROUTER_API_KEYOLLAMA_API_KEY
Provider-specific configuration:
| Variable | Description |
|---|---|
OLLAMA_BASE_URL |
Ollama-compatible server URL. |
OPENROUTER_BASE_URL |
OpenRouter-compatible API URL. |
OPENROUTER_SITE_URL / OPENROUTER_SITE_NAME |
Override Iris Agent's OpenRouter attribution URL and display name. Defaults to the Iris Agent GitHub repository and Iris Agent. Requests include HTTP-Referer, X-OpenRouter-Title, and the cli-agent,ide-extension categories. |
ANTHROPIC_BETA / ANTHROPIC_BETAS |
Optional Anthropic beta headers. |
HF_TOKEN |
Hugging Face authentication where required by a configured provider. |
| Variable | Description |
|---|---|
PORT |
HTTP listening port; defaults to 8080. |
AGENT_ALLOWED_ORIGINS |
Comma-separated browser origins allowed by CORS. Requests without an Origin header are allowed. |
DATABASE_URL |
Database connection URL used by the configured agent storage. |
IRIS_AGENT_RUNS_DB_PATH |
SQLite path for persisted run lifecycle data. |
IRIS_BACKEND_SERVICE |
Selects the backend service integration. |
IRIS_AGENT_PREFERRED_AGENT_ID |
Preferred external agent identifier. |
IRIS_AGENT_EXTERNAL_AGENT_MANIFEST_PATH |
Path to an external-agent manifest. |
IRIS_AGENT_GIT_SAFETY_MODE |
Git safety policy; defaults to suggest. |
IRIS_AGENT_AUTO_LINT / IRIS_AGENT_AUTO_TEST |
Enable automatic lint/test validation. |
IRIS_AGENT_LINT_CMD / IRIS_AGENT_TEST_CMD |
Override validation commands. |
IRIS_AGENT_AUTO_FIX_VALIDATION |
Enable automatic validation fixes. |
IRIS_AGENT_INPUT_TOKEN_LIMIT / IRIS_AGENT_MAX_OUTPUT_TOKENS |
Token-budget controls. |
IRIS_AGENT_PROMPT_TOKEN_BUDGET_RATIO |
Prompt budget ratio. |
IRIS_AGENT_MODEL_RETRY_ATTEMPTS |
Model request retry count. |
IRIS_AGENT_REFLECTION_MAX / IRIS_AGENT_REFLECTION_MAX_STEPS |
Reflection-loop limits. |
IRIS_AGENT_REFLECTION_RETRY_ATTEMPTS |
Reflection retry count. |
IRIS_AGENT_REFLECTION_NO_PROGRESS_REPEATS |
Maximum repeated no-progress reflection cycles. |
IRIS_ENABLE_SLASH_COMMANDS |
Enable slash-command handling. |
IRIS_DEBUG_TOKEN_USAGE_SOURCE |
Enable token-usage diagnostics. |
IRIS_AGENT_STREAM_RETRY_ENABLED |
Enable stream retries. Related retry delay and limit variables are supported by the runtime. |
IRIS_VERBOSE_SKILL_DISCOVERY |
Enable verbose skill-discovery logging. |
BROWSER_NO_SANDBOX |
Set to true only when browser automation must run without a sandbox. |
Browser-backed web search uses puppeteer, which downloads a compatible
Chrome for local and packaged installations. Set BROWSER_NO_SANDBOX=true
only when browser automation must run without a sandbox.
Environment values can be supplied in a local .env file for the HTTP server
because it loads dotenv/config. Do not commit .env files or API keys.
Desktop-only routes require both TAURI_BUNDLED=1 and IRIS_DESKTOP_TOKEN.
Clients send the token in the X-Desktop-Token header. These protected routes
include key management, file operations, remote chat-session synchronization,
and MCP inspection/calls. Do not expose the desktop token to browsers.
Run lifecycle data is stored in SQLite. Set IRIS_AGENT_RUNS_DB_PATH to choose
the database location; otherwise it is stored at ~/.iris/agent-runs.sqlite.
Chat sessions are stored as JSON files under ~/.iris/chat-sessions/ and are
managed through the desktop synchronization routes.
MCP servers are supplied in chat requests (mcpServers field) or MCP route
payloads. Use POST /api/agent/mcp/inspect to discover tools before calling
POST /api/agent/mcp/call. Both routes require desktop authentication (see
Desktop authentication). MCP tool names are
generated as mcp_<server name>_<tool name> and must start with mcp_.
Each server config has an id, name, enabled flag, and either a local
command or a remote url (exactly one connection mode is required). If
both are supplied, url takes precedence and command is dropped during
sanitization to avoid an ambiguous configuration. If url is present but
malformed or unsupported (not http/https), the whole config is rejected
rather than silently falling back to command - a saved remote server must
never be resurrected as a local process launch.
| Field | Type | Applies to | Description |
|---|---|---|---|
id |
string |
both | Stable identifier for the server (auto-generated if omitted). |
name |
string |
both | Display name; also used to build tool keys (mcp_<name>_<tool>). If omitted for a remote server, defaults to the URL's hostname (not the full URL) to avoid leaking embedded credentials. |
enabled |
boolean |
both | Servers with enabled: false are skipped. |
command |
string |
local | Executable to spawn (e.g. npx, uvx, or an absolute path). |
args |
string[] |
local | Arguments passed to command. |
env |
object |
local | Extra environment variables merged into the spawned process's env. |
url |
string |
remote | HTTP(S) endpoint for a remote MCP server. |
headers |
object |
remote | Extra HTTP headers (e.g. Authorization) sent with every request. |
Local stdio servers use command, args, and optional env fields:
{
"id": "github-local",
"name": "GitHub",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "..." },
"enabled": true
}Remote MCP servers use an HTTP(S) url and may provide request headers for
authentication. Iris connects using the MCP Streamable HTTP transport (the
legacy SSE transport is not supported; the remote server must implement
Streamable HTTP):
{
"id": "remote-tools",
"name": "Remote tools",
"url": "https://example.com/mcp",
"headers": { "Authorization": "Bearer ..." },
"enabled": true
}Only use remote URLs and credentials from trusted configuration. Remote MCP
servers can execute actions and return untrusted content on the agent's behalf.
Prefer the headers field for auth tokens over embedding them in the URL's
query string or userinfo — Iris redacts query strings, userinfo, and
fragments from URLs shown in model-facing tool descriptions, generated docs,
warning logs, and the /mcp/inspect response; the raw url (including any
embedded credentials) is only ever used for the actual transport connection.
Connects to a single server, lists its tools, and closes the connection. Useful for validating a server config before enabling it for chat.
Request body:
{ "server": { "id": "remote-tools", "name": "Remote tools", "url": "https://example.com/mcp", "headers": {}, "enabled": true } }Response body:
{
"success": true,
"server": { "id": "remote-tools", "name": "Remote tools", "command": "", "url": "https://example.com/mcp" },
"tools": [{ "name": "search", "description": "Search the knowledge base" }],
"toolCount": 1
}server.url in the response is redacted (userinfo, query string, and
fragment stripped) even though the real, unredacted url was used to
connect - the same redaction applied everywhere else a remote URL is
surfaced (see above).
On failure (e.g. connection or auth error), the response is
{ "success": false, "error": "..." } with a 500 status.
Request body:
{
"toolName": "mcp_Remote_tools_search",
"args": { "query": "..." },
"workspaceRoot": "/path/to/workspace",
"mcpServers": [{ "id": "remote-tools", "name": "Remote tools", "url": "https://example.com/mcp", "headers": {}, "enabled": true }]
}toolName must match the mcp_<server name>_<tool name> format and the
server that owns the tool must be present in mcpServers. The response
mirrors the MCP SDK's callTool result (success, server, tool,
isError, content, structuredContent, and error when applicable).
Commands that require approval pause until the client submits
POST /api/agent/command-confirmation with a confirmationId and boolean
approved value.
The stdio server implements the standard ACP v1 lifecycle:
initializesession/new- supports initialmodelIdspecification in_metaor parameterssession/load- load existing session history with optional model overridesession/set_config_option- dynamically change session options such as model (configId: "model")session/prompt- execute prompts with optional per-turnmodelIdoverride in_metasession/cancel- abort active turnsession/close- clean up session resourcessession/updatenotifications for assistant text, reasoning, and tool status
The server supports dynamic model resolution and caching across ACP requests.
Clients can specify and change models at multiple levels:
- Server Default: Pass
--modelId <model>toiris-agent --acpor set theMODEL_ID/OPENROUTER_MODELenvironment variable. - Session Creation / Load: Pass
modelIdin_meta.iris.modelIdor_meta.modelIdduringsession/neworsession/load. - Dynamic Switch: Call
session/set_config_optionwithconfigId: "model"andvalue: "<modelId>"to change the active model for subsequent prompts. - Per-Prompt Override: Include
_meta.iris.modelIdor_meta.modelIdin thesession/promptpayload to run a single prompt turn with a specific model.
Install the published package in the Node.js process that owns your IDE's agent integration:
npm install @4onstudios/iris-agentThe spawned agent uses the provider credentials from its environment. For example:
OPENROUTER_API_KEY=... npm run your-ide-backendWhen using a local checkout instead of the published package, build it before starting the compiled CLI:
npm install
npm run buildWhen installing from a Git branch or commit, npm runs this package's
prepare script to build dist/ during installation, so root exports such as
IrisClient are available without manually committing generated artifacts.
The ACP subprocess writes protocol messages to stdout and diagnostic logs to stderr. Never merge logs into stdout or pipe stdout through a text logger; doing so corrupts the ACP stream.
IDE extensions written for Node.js can use the exported IrisClient instead of
managing ACP messages or the agent subprocess directly:
import { IrisClient } from "@4onstudios/iris-agent";
const { client } = await IrisClient.spawn({
cwd: workspaceRoot,
onSessionUpdate(notification) {
renderAgentUpdate(notification.update);
},
});
try {
// Open session with an optional specific model
await client.openSession(workspaceRoot, {
modelId: "openrouter/anthropic/claude-3.7-sonnet",
});
// Prompt the agent
const result = await client.prompt("Explain the selected code");
console.log(result.stopReason);
// Switch model dynamically mid-session
await client.setModel("openrouter/openai/gpt-4o");
// Or override model for a single prompt
await client.prompt("Refactor this function", {
modelId: "openrouter/google/gemini-2.0-flash",
});
} finally {
await client.close();
}IrisClient.spawn() starts iris-agent --workspace <cwd> --acp, initializes the
ACP connection, and owns process cleanup. The cwd must be the workspace path
the agent is allowed to access. The package's iris-agent executable must be
available on PATH; use command and args when your IDE starts a local
checkout or a custom launcher:
const { client } = await IrisClient.spawn({
command: "node",
args: ["/path/to/iris-agent/dist/cli.js", "--workspace", workspaceRoot, "--acp"],
cwd: workspaceRoot,
env: { OPENAI_API_KEY: process.env.OPENAI_API_KEY },
});One IrisClient owns one active ACP session. Call openSession() before
prompt(), use cancel() to stop the active turn, and call closeSession()
to end a session in the current workspace. An ACP process is bound to its
startup workspace: to switch workspaces, call close() and use spawn() to
create a new client for the new workspace. close() also terminates a process
created by spawn() and is safe to call repeatedly.
IrisClient.connect() accepts an existing ACP stream or in-process ACP agent
when the IDE manages the process or transport itself. Use this for IDEs that
already have a process supervisor or ACP transport. spawn() rejects if the
agent executable cannot start or initialization fails, so handle startup errors
before enabling agent commands in the UI.
The onSessionUpdate callback receives standard ACP session notifications:
| Update | Typical UI behavior |
|---|---|
agent_message_chunk |
Append assistant text. |
agent_thought_chunk |
Show or hide reasoning according to IDE policy. |
tool_call |
Show a tool as running. |
tool_call_update |
Update tool status and output. |
Do not expose provider API keys or raw ACP stdio streams to a browser renderer. Keep the client in the trusted desktop/backend process and forward only the events and commands your IDE UI needs.
Use IrisClient unless your integration must own the ACP process and
JSON-RPC transport. The following Node.js example starts the published CLI
with npx, performs the ACP v1 handshake, streams updates, and closes the
session and child process reliably:
import { spawn } from "node:child_process";
import readline from "node:readline";
type JsonRpcResponse = {
id?: number;
result?: unknown;
error?: { code: number; message: string };
method?: string;
params?: {
update?: {
sessionUpdate?: string;
content?: { text?: string };
title?: string;
};
};
};
async function connectToIrisAcp() {
const workspaceRoot = process.cwd();
const agentProcess = spawn(
"npx",
["--yes", "@4onstudios/iris-agent", "--workspace", workspaceRoot, "--acp"],
{
cwd: workspaceRoot,
stdio: ["pipe", "pipe", "inherit"],
env: process.env,
},
);
let nextMessageId = 1;
const pendingRequests = new Map<
number,
{ resolve: (result: unknown) => void; reject: (error: Error) => void }
>();
const rejectPendingRequests = (error: Error) => {
for (const { reject } of pendingRequests.values()) reject(error);
pendingRequests.clear();
};
agentProcess.once("error", rejectPendingRequests);
agentProcess.once("exit", (code, signal) => {
rejectPendingRequests(
new Error(`Iris ACP process exited (${signal ?? `code ${code ?? "unknown"}`})`),
);
});
const sendRequest = (method: string, params?: unknown): Promise<unknown> => {
const id = nextMessageId++;
agentProcess.stdin.write(
`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}\n`,
);
return new Promise((resolve, reject) => {
pendingRequests.set(id, { resolve, reject });
});
};
const lines = readline.createInterface({ input: agentProcess.stdout });
lines.on("line", (line) => {
if (!line.trim()) return;
const message = JSON.parse(line) as JsonRpcResponse;
if (typeof message.id === "number" && pendingRequests.has(message.id)) {
const request = pendingRequests.get(message.id)!;
pendingRequests.delete(message.id);
if (message.error) {
request.reject(new Error(message.error.message));
} else {
request.resolve(message.result);
}
return;
}
const update = message.params?.update;
if (message.method === "session/update") {
if (update?.sessionUpdate === "agent_message_chunk") {
process.stdout.write(update.content?.text ?? "");
} else if (update?.sessionUpdate === "tool_call") {
console.log(`\n[Executing tool: ${update.title ?? "unknown"}]`);
}
}
});
try {
await sendRequest("initialize", {
protocolVersion: 1,
clientInfo: { name: "my-custom-service", version: "1.0.0" },
clientCapabilities: {},
});
const session = (await sendRequest("session/new", {
cwd: workspaceRoot,
mcpServers: [],
})) as { sessionId: string };
await sendRequest("session/prompt", {
sessionId: session.sessionId,
prompt: [
{
type: "text",
text: "List all files in the root folder and summarize the project.",
},
],
});
await sendRequest("session/close", { sessionId: session.sessionId });
} finally {
lines.close();
if (!agentProcess.killed) agentProcess.kill();
}
}
connectToIrisAcp().catch(console.error);Set OPENAI_API_KEY, OPENROUTER_API_KEY, or the API key for the selected
provider in the environment before starting the client. Keep those credentials
in the trusted Node.js process; do not pass them to a browser renderer.
The CLI does not load MCP definitions from AIRIS_MCP_SERVERS. In the current
ACP server, custom MCP servers are not yet applied from session/new either,
so leave mcpServers empty as shown. To use custom MCP tools today, configure
them through the authenticated HTTP agent API rather than this ACP example.
ENOENTwhen callingspawn()means the configuredcommandis not onPATH. Setcommandandargsto the compiled CLI, or install the package globally for the IDE process.- An initialization failure usually means the subprocess exited early, the
provider key is missing, or stdout contains non-ACP output. Inspect stderr
and verify the provider environment passed through
env. - A prompt requires an open session. Call
openSession()once per workspace. To switch workspaces, callclose()to terminate the current ACP process, then usespawn()to create a client bound to the new workspace. IrisClientrequires a Node.js desktop/backend process. Browser-only IDE clients should call their backend over HTTPS/WebSocket/IPC instead of spawning the agent in the renderer.
For a custom IDE, keep IrisClient in the desktop or backend process and
forward agent updates to the UI over WebSocket, IPC, or the IDE's event bus:
import { IrisClient } from "@4onstudios/iris-agent";
export class IrisAgentController {
private client?: IrisClient;
async start(workspaceRoot: string, onUpdate: (update: unknown) => void) {
const spawned = await IrisClient.spawn({
cwd: workspaceRoot,
clientName: "my-custom-ide",
clientVersion: "1.0.0",
onSessionUpdate(notification) {
onUpdate(notification.update);
},
});
this.client = spawned.client;
await this.client.openSession(workspaceRoot);
}
async prompt(prompt: string) {
if (!this.client) throw new Error("Iris agent is not running");
return this.client.prompt(prompt);
}
async cancel() {
await this.client?.cancel();
}
async stop() {
await this.client?.close();
this.client = undefined;
}
}Example backend routes can forward updates to the custom IDE client:
const iris = new IrisAgentController();
await iris.start(workspaceRoot, (update) => {
websocket.broadcast({ type: "agent-update", update });
});
app.post("/api/agent/prompt", async (request, response) => {
response.json(await iris.prompt(request.body.prompt));
});
app.post("/api/agent/cancel", async (_request, response) => {
await iris.cancel();
response.sendStatus(204);
});
app.post("/api/agent/stop", async (_request, response) => {
await iris.stop();
response.sendStatus(204);
});Handle forwarded updates in the IDE UI using sessionUpdate:
function handleAgentUpdate(update: any) {
switch (update.sessionUpdate) {
case "agent_message_chunk":
appendAssistantText(update.content.text);
break;
case "agent_thought_chunk":
appendReasoning(update.content.text);
break;
case "tool_call":
showToolStarted(update.title, update.toolCallId);
break;
case "tool_call_update":
updateToolStatus(update.toolCallId, update.status);
break;
}
}The integration flow is:
Custom IDE UI -> IDE backend -> IrisClient.spawn()
-> iris-agent --acp -> ACP session/update events
-> IDE backend -> WebSocket/IPC -> Custom IDE UI
npm run typecheck
npm run build
npm testRelease validation and publishing are scripted:
npm run release
NPM_CONFIG_OTP=<code> npm run release:publishnpm run release requires a clean worktree, runs type checking, tests, build,
and npm pack --dry-run. npm run release:publish performs the same checks
before publishing with public npm access. Use Node.js >=22.13.0, matching the
package engine requirement.
The repository also provides Make targets:
make test # npm test with Jest's serial/forced-exit flags
make run # npm start
make dev # npm run devFor production, run the compiled output from a process supervisor, restrict
AGENT_ALLOWED_ORIGINS, keep provider credentials in a secret store, protect
the HTTP service behind TLS/authentication, and use a writable persistent
location for the run database.
Contributions are welcome. See CONTRIBUTING.md for setup, validation, and pull request guidance. Use the repository's issue templates for bug reports and feature requests. See CODE_OF_CONDUCT.md for community expectations and SECURITY.md for private vulnerability reporting.
The project logo is available at
assets/iris-agent-logo.svg for repository and
community references. Keep the logo unchanged when using it as the project
mark.
The published package is available on npm.
Yarn users can install the published package with:
yarn global add @4onstudios/iris-agentThis project is released under the MIT License.