Built by your agents. Run by Executor.
Bring your own agents. Build tools, automations, and apps once. Run them on Executor and use them across all your agents.
Rather keep it on your machine? Desktop, CLI, or self-hosted ↓
Start with something useful.
Bring a tool you already use, give your agent a skill, or ask it to build something you wish existed. Start small. You can change it as you go.
An existing tool
Bring an MCP or API you already use.
A skill
Teach your agents how you like things done.
Something new
Describe it. Your agent writes the code.
Ask for what you actually need.
Executor is not an agent. It’s a place for your agent to build and run software.
Ask your agent
“Pull together my signups and open issues. Give my agents one way to check what's changed.”
Your agent builds a reusable tool and deploys it to Executor.
Uses your accounts
A tool your agents can call
daily_brief.get_changes()
Signups and open issues, compared with the last check.
Let it keep working.
Give that tool a schedule. Keep a history of what it finds. Add a page you can open. Executor runs the app, even after the conversation ends.
“Run this every weekday at 9. Save the results and build me a page to read them.”
A strong start to the week.
New signups
1,284
+12% from last report
Signups are up 12%, and there are four fewer open issues. Organic search brought in the most new users.
That's an Executor app.
A tool your agent can call. An automation that runs on its own. An interface you can use. They can all be parts of the same app, built around what you need.
Tools
Call anything, it's just JavaScript.
Skills
Give your agent instructions it can use again.
UI
A page for your app, at its own URL.
Storage
Keep data and state between runs.
Triggers
Run on a schedule or respond to webhooks.
Workflows
Durable work across multiple steps.
It's just code
And you don't have to write it
import { defineApp } from "apps";
import { github } from "./providers";
import { database } from "./database";
import { listBriefs, saveBrief, refreshBrief } from "./tools";
import { refresh } from "./workflows";
export const requirements = {
accounts: { github },
database,
};
export default defineApp(requirements, {
queries: { listBriefs },
mutations: { saveBrief, refreshBrief },
workflows: { refresh },
});The entry point
Brings the accounts, data, tools, and workflows together in one app.
import { query, mutation, object, string, array } from "apps";
import type { QueryContext, MutationContext } from "apps";
import type { requirements } from "./index";
import { Brief, BriefInput } from "./database";
type Read = QueryContext<typeof requirements>;
type Write = MutationContext<typeof requirements>;
export const Repository = object({
owner: string(),
name: string(),
});
export const listBriefs = query(
{ input: object({}), output: array(Brief) },
async ({ db }: Read) =>
db.briefs.withIndex("by_creation").order("desc").take(10),
);
export const saveBrief = mutation(
{ input: BriefInput, output: Brief },
async ({ db }: Write, input) => db.briefs.insert(input),
);
export const refreshBrief = mutation(
{ input: Repository },
async (ctx: Write, input) =>
ctx.workflows.start({ workflow: "refresh", input }),
);Tools for your agent
Ordinary functions to read, save, and refresh a brief. Your agent can call them as tools.
import { defineProvider, oauth2 } from "apps";
export const github = defineProvider({
name: "GitHub",
auth: {
oauth: oauth2({
authorizationUrl: "https://github.com/login/oauth/authorize",
tokenUrl: "https://github.com/login/oauth/access_token",
scopes: ["repo"],
}),
},
});Connected accounts
Declares the service this app uses. Executor handles sign-in and keeps credentials out of these files.
import { defineDatabase, table, object, string, number } from "apps";
const fields = {
repository: string(),
openIssues: number(),
};
export const BriefInput = object(fields);
export const Brief = object({ id: string(), ...fields });
export const database = defineDatabase({
briefs: table(fields),
});Data that stays
Defines the records your app saves. Briefs stay available between runs and appear in the UI.
import { workflow, object, number } from "apps";
import type { WorkflowContext } from "apps";
import type { requirements } from "./index";
import { Repository, saveBrief } from "./tools";
type Context = WorkflowContext<typeof requirements>;
const Stats = object({ open_issues_count: number() });
export const refresh = workflow(
{ input: Repository },
async (ctx: Context, { owner, name }) => {
const repository = owner + "/" + name;
const openIssues = await ctx.step.do("Read GitHub", async (step) => {
const path = encodeURIComponent(owner) + "/" + encodeURIComponent(name);
const response = await step.fetch("https://api.github.com/repos/" + path, {
headers: {
Authorization: "Bearer " + step.accounts.github.fields.access_token,
"User-Agent": "Daily brief",
},
});
if (!response.ok) throw new Error("Could not read the repository");
return Stats.parse(await response.json()).open_issues_count;
});
return ctx.step.runMutation("Save the brief", saveBrief, {
repository,
openIssues,
});
},
);Work in the background
Reads from GitHub, then saves the result. Executor tracks each step so work can resume after an interruption.
---
name: brief
description: Review recent GitHub activity with Daily brief.
---
# Give me a project brief
1. Ask which repository to review if it is not clear.
2. Use refreshBrief with the repository owner and name.
3. Wait for the workflow to finish, then call listBriefs.
4. Summarize the saved open issue count in plain language.
Mention which repository the brief covers.
If the refresh fails, say so instead of presenting old data as new.
Follow the user's instructions and Executor's approval requests.Instructions your agent can follow
A Markdown file teaches your agent when to use the app and how to work with its tools.
import { createRoot } from "react-dom/client";
import { array } from "apps";
import { createAppClient, queryReference } from "apps/client";
import { useAppQuery } from "apps/react";
import type { listBriefs } from "../tools";
import { Brief } from "../database";
const client = createAppClient();
const briefs = client.queryAtom(
queryReference<typeof listBriefs>("listBriefs"),
{},
array(Brief),
);
function DailyBrief() {
const { data, pending, error } = useAppQuery(briefs);
if (pending) return <p>Loading your briefs…</p>;
if (error) return <p>Could not load your briefs.</p>;
return (
<main>
<h1>Your project briefs</h1>
{data?.map((brief) => (
<article key={brief.id}>
<h2>{brief.repository}</h2>
<p>{brief.openIssues} open issues</p>
</article>
))}
</main>
);
}
const root = document.getElementById("root");
if (!root) throw new Error("Missing app root");
createRoot(root).render(<DailyBrief />);An interface for you
A React page that reads the same data as your agent. Executor gives it its own URL.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Daily brief</title>
<script type="module" src="./main.tsx"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>The page shell
A normal HTML document loads your app's interface.
{
"name": "daily-brief",
"private": true,
"type": "module",
"dependencies": {
"react": "^19.2.5",
"react-dom": "^19.2.5"
}
}Use the libraries you know
Add packages from npm when you need them. This app uses React for its interface.
One app. Ordinary files.Choose a file to look inside.
Run it where you want
Use Executor Cloud, or run the same setup on your own machine or server.
npm i -g executorWhat people say
From people using Executor to connect their agents to their tools.
Pricing
Cloud is free for up to three people. Team is $15 per member per month. Running it yourself is free.
Writing
About
Deploying an app for your agent should be as simple as deploying a website. Executor is where your personal software lives.