<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>KHTDR – agents</title><description/><link>https://khtdr.com/agents/</link><lastBuildDate>Tue, 15 Sep 2026 00:00:00 -0600</lastBuildDate><language>en-us</language><managingEditor>mazzarelli@gmail.com (Joey Mazzarelli)</managingEditor><webMaster>mazzarelli@gmail.com (Joey Mazzarelli)</webMaster><atom:link href="https://khtdr.com/agents/index.xml" rel="self" type="application/rss+xml"/><item><title>agents: how it compares</title><description> Overview · Getting started · Features · Tools · Running code · CLI, API, embedding · Operating it · How it compares Two projects may come up when you are looking for similar solutions. Flue, an open TypeScript framework from the Astro team. Sapiom, a hosted platform. They are different enough from each other that the comparisons barely overlap. Both were checked in September 2026, and both move fast, so treat the specifics as a snapshot. The short version This Flue Sapiom What an agent is A row in a database A TypeScript function TypeScript step code Changing a prompt Save it Commit, build, deploy Commit, build, deploy Where it runs Your laptop, your host, or inside your app Your host or Cloudflare Their cloud only Model keys Yours, via OpenRouter Yours, per provider Theirs, five models Per-run cost None None $0.50 to $1 Version history and replay Yes Git, and no replay No Survives a redeploy mid-run Partly Yes, by design Yes Sandbox Capped subprocess Managed cloud boxes Managed cloud boxes Slack, GitHub, Stripe ingress Write it yourself 17 verified channels Webhook triggers Auth and tenancy None None Yes, with SSO Against Flue Flue is Apache-2.0, self-hosted, bring your own model key, no account, no per-run fee. Everything I would normally compare is a tie. What is left is a disagreement about what an agent is.</description><link>https://khtdr.com/agents/compare/</link><guid>https://khtdr.com/agents/compare/</guid><pubDate>Wed, 16 Sep 2026 00:00:00 -0600</pubDate><author>mazzarelli@gmail.com (Joey Mazzarelli)</author><content:encoded>
&lt;![CDATA[
&lt;link rel="stylesheet" href="https://khtdr.com/css/agents.css" /&gt;
&lt;nav class="agents-nav"&gt;&lt;a href="https://khtdr.com/agents"&gt;Overview&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/start"&gt;Getting started&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/features"&gt;Features&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/tools"&gt;Tools&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/sandbox"&gt;Running code&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/cli-api"&gt;CLI, API, embedding&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/running"&gt;Operating it&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;span class="here"&gt;How it compares&lt;/span&gt;&lt;/nav&gt;
&lt;p class="agents-hero"&gt;
Two projects may come up when you are looking for similar solutions. Flue, an
open TypeScript framework from the Astro team. Sapiom, a hosted platform. They
are different enough from each other that the comparisons barely overlap. Both
were checked in September 2026, and both move fast, so treat the specifics as a
snapshot.
&lt;/p&gt;
&lt;h2 id="the-short-version"&gt;The short version&lt;/h2&gt;
&lt;div class="agents-table-wrap"&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;This&lt;/th&gt;
&lt;th&gt;Flue&lt;/th&gt;
&lt;th&gt;Sapiom&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What an agent is&lt;/td&gt;
&lt;td&gt;A row in a database&lt;/td&gt;
&lt;td&gt;A TypeScript function&lt;/td&gt;
&lt;td&gt;TypeScript step code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Changing a prompt&lt;/td&gt;
&lt;td&gt;Save it&lt;/td&gt;
&lt;td&gt;Commit, build, deploy&lt;/td&gt;
&lt;td&gt;Commit, build, deploy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where it runs&lt;/td&gt;
&lt;td&gt;Your laptop, your host, or inside your app&lt;/td&gt;
&lt;td&gt;Your host or Cloudflare&lt;/td&gt;
&lt;td&gt;Their cloud only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model keys&lt;/td&gt;
&lt;td&gt;Yours, via OpenRouter&lt;/td&gt;
&lt;td&gt;Yours, per provider&lt;/td&gt;
&lt;td&gt;Theirs, five models&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-run cost&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;$0.50 to $1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Version history and replay&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Git, and no replay&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Survives a redeploy mid-run&lt;/td&gt;
&lt;td&gt;Partly&lt;/td&gt;
&lt;td&gt;Yes, by design&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sandbox&lt;/td&gt;
&lt;td&gt;Capped subprocess&lt;/td&gt;
&lt;td&gt;Managed cloud boxes&lt;/td&gt;
&lt;td&gt;Managed cloud boxes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slack, GitHub, Stripe ingress&lt;/td&gt;
&lt;td&gt;Write it yourself&lt;/td&gt;
&lt;td&gt;17 verified channels&lt;/td&gt;
&lt;td&gt;Webhook triggers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth and tenancy&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Yes, with SSO&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h1 id="against-flue"&gt;Against Flue&lt;/h1&gt;
&lt;p&gt;Flue is Apache-2.0, self-hosted, bring your own model key, no account, no per-run
fee. Everything I would normally compare is a tie. What is left is a disagreement
about what an agent is.&lt;/p&gt;
&lt;h2 id="the-disagreement"&gt;The disagreement&lt;/h2&gt;
&lt;p&gt;In Flue an agent is a function with a React-style hooks API:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;&amp;#39;use agent&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Triage() {&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;useModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;anthropic/claude-sonnet-4-6&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;useSandbox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;useTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;searchIssues&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;useSkill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reviewChecklist&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Investigate the reported issue and recommend the next action.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The function re-renders before every model call. Hooks get re-evaluated each turn,
so capabilities appear and disappear mid-conversation. You build it with Vite and
deploy to Node or Cloudflare, where each conversation becomes a Durable Object.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The unit of work is different.&lt;/strong&gt; Flue&amp;rsquo;s unit is a conversation. It is a
stateful, addressable, long-lived service at a URL, with durable history and a
contract that survives crashes and redeploys. This project&amp;rsquo;s unit is a run. It has
an input, an output, a cost, and a tree of children. Flue asks what an agent knows
and can reach right now. This asks what happened on that run, and whether you can
do it again differently.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Data versus code.&lt;/strong&gt; Here an agent is a row. It gets versioned on every save,
replayed against an older version, and edited by someone who does not write
TypeScript. In Flue an agent is source in a repo. Changing a prompt is a commit, a
build, and a deploy, and there is no dashboard, no version history, and no replay.
That is not an oversight. It is the same call Astro makes about pages. It does
mean the whole A/B-and-replay half of this project has no counterpart there.&lt;/p&gt;
&lt;h2 id="side-by-side"&gt;Side by side&lt;/h2&gt;
&lt;div class="agents-table-wrap"&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;This project&lt;/th&gt;
&lt;th&gt;Flue&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;License and backing&lt;/td&gt;
&lt;td&gt;Private repo, one author, npm &lt;code&gt;@upship/agents&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Apache-2.0, Astro team, ~8.3k stars, 1.0 beta&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Defining an agent&lt;/td&gt;
&lt;td&gt;A JSON row: prompt, tier, tools, optional graph config&lt;/td&gt;
&lt;td&gt;A TypeScript function with &lt;code&gt;use*&lt;/code&gt; hooks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who writes it&lt;/td&gt;
&lt;td&gt;A person in the UI, an interview, or a coding agent from the generated brief&lt;/td&gt;
&lt;td&gt;You, in an editor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Changing a prompt&lt;/td&gt;
&lt;td&gt;Save, new version snapshot&lt;/td&gt;
&lt;td&gt;Edit source, commit, build, deploy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Versioning and replay&lt;/td&gt;
&lt;td&gt;Every save snapshots. Replay any run against another version or swapped tools&lt;/td&gt;
&lt;td&gt;None. Git is the history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unit of work&lt;/td&gt;
&lt;td&gt;A run, with a status, a cost, and a child tree&lt;/td&gt;
&lt;td&gt;A conversation at a URL, with an append-only stream&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Execution modes&lt;/td&gt;
&lt;td&gt;Freeform loop, DAG with conditions and merges, plan-then-execute, debate, reflection, quality gate, peer messaging&lt;/td&gt;
&lt;td&gt;One model-driven turn loop. Branching is conditional hooks. Delegation is a &lt;code&gt;task&lt;/code&gt; tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orchestration authored as&lt;/td&gt;
&lt;td&gt;Config fields&lt;/td&gt;
&lt;td&gt;Control flow you write&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sub-agents&lt;/td&gt;
&lt;td&gt;An agent is a tool. Child runs roll up cost. Peers can message each other&lt;/td&gt;
&lt;td&gt;&lt;code&gt;useSubagent()&lt;/code&gt;, fresh context, isolated from the parent, depth capped at 4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Durability&lt;/td&gt;
&lt;td&gt;Graph nodes park and resume by token, and a run answered but not yet restarted is picked up at boot. Freeform runs wait in-process and die with it&lt;/td&gt;
&lt;td&gt;The headline feature. Leases, heartbeats, startup reconciliation, classified recovery, durable tools with checkpoints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;Four drivers behind one port, with a conformance suite&lt;/td&gt;
&lt;td&gt;Adapters for SQLite, Postgres, libSQL, MySQL, MongoDB, Redis. Free on Cloudflare&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sandboxing&lt;/td&gt;
&lt;td&gt;Capped subprocess. No filesystem namespace. Reports what it could not enforce&lt;/td&gt;
&lt;td&gt;Virtual, local, or remote providers: E2B, Daytona, Modal, Vercel, Cloudflare&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tools&lt;/td&gt;
&lt;td&gt;~22 first-party plus MCP. Definitions are rows you can A/B and bind config to&lt;/td&gt;
&lt;td&gt;&lt;code&gt;defineTool()&lt;/code&gt; with Valibot, composed per render, conditionally visible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skills&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;First-class, using the open agentskills.io format, progressively disclosed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Human in the loop&lt;/td&gt;
&lt;td&gt;Parking tools, a derived answer form, a pending inbox in UI and CLI&lt;/td&gt;
&lt;td&gt;Nothing named. A person sends the next message. No inbox&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Models&lt;/td&gt;
&lt;td&gt;OpenRouter, one key. Tiers mapped as data, repointable without a rebuild&lt;/td&gt;
&lt;td&gt;&lt;code&gt;useModel()&lt;/code&gt; per agent, keys per provider, automatic compaction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Triggers&lt;/td&gt;
&lt;td&gt;None. Cron the CLI&lt;/td&gt;
&lt;td&gt;No scheduler, but documented patterns per target&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inbound integrations&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;17 verified channels: Slack, Discord, GitHub, Linear, Stripe, Twilio and more&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;Its own admin UI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@flue/react&lt;/code&gt; for your product&amp;rsquo;s chat UI, plus streaming data parts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client SDK&lt;/td&gt;
&lt;td&gt;None yet&lt;/td&gt;
&lt;td&gt;Small, typed, one dependency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observability&lt;/td&gt;
&lt;td&gt;Run tree, live node events, cost columns, side-by-side comparison, in a UI&lt;/td&gt;
&lt;td&gt;An event stream you ship to Sentry, Braintrust, or OpenTelemetry. No dashboard, by choice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evals&lt;/td&gt;
&lt;td&gt;None. Replay is the nearest thing&lt;/td&gt;
&lt;td&gt;Vitest suites, judges, CI reporting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;None. Binds to localhost for that reason&lt;/td&gt;
&lt;td&gt;None built in, but you own the route map&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h2 id="what-flue-does-better"&gt;What Flue does better&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Durability.&lt;/strong&gt; This is the gap that matters most. Every admitted submission
reaches exactly one outcome, enforced by attempts, leases, startup
reconciliation, and a classifier that tells a partial stream from an unresolved
tool batch from a context overflow. Here, a freeform run waits in-process and
dies with it. The heartbeat notices a dead run. It does not recover one. There
is one recovery path and it is narrow: a paused run that got its answer but
never got restarted is picked back up on the next boot, so a deploy cannot
strand a run that had nothing left to wait for. A run that was mid-step when
the process died is still closed out as an error.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sandbox isolation from a provider.&lt;/strong&gt; Managed Linux per conversation, with a
real filesystem boundary and a package manager. &lt;code&gt;run_script&lt;/code&gt; here covers
computation and says plainly what it could not take away. It will not hand a
coding agent a disposable box.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Channels.&lt;/strong&gt; Verified webhook ingress from 17 providers, with signature
checking, replay windows, and idempotency keys. Getting a GitHub issue into an
agent here is a service you write.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Skills.&lt;/strong&gt; A portable format for packaged expertise, disclosed progressively
so an agent can carry dozens cheaply. No equivalent here.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Conversational agents.&lt;/strong&gt; Long-lived services at a URL, joined by many people
over days. Runs here are jobs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A frontend story and a client SDK.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Evals, compaction, and OpenTelemetry.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A team, a license, and a community.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="what-this-project-does-better"&gt;What this project does better&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Agents as data.&lt;/strong&gt; Version history, a UI, replay against an older version,
side-by-side comparison. The whole precondition for A/B work.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tool definitions as data.&lt;/strong&gt; Many definitions over one implementation, each
with its own wording, config, and secrets. Both projects have a &lt;code&gt;defineTool()&lt;/code&gt;
now. The unit is still different: theirs binds a schema once, mine is a row you
can point at two accounts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Declarative multi-step orchestration.&lt;/strong&gt; Conditional edges, fan-in merges,
gates with retry, plan-then-execute with human review, debate, reflection.
In Flue each of those is control flow you hand-write.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Peer messaging&lt;/strong&gt; between concurrent nodes, with deadlock detection. Flue&amp;rsquo;s
delegation is strictly hierarchical and siblings never talk.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Human gates with an inbox.&lt;/strong&gt; Flue makes asking natural and gives you nothing
for finding what is waiting.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Durable memory with compare-and-set.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Model tiers as data.&lt;/strong&gt; Repoint &lt;code&gt;reasoning&lt;/code&gt; without a rebuild, and old runs
keep the id they resolved to.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cost you can look at.&lt;/strong&gt; Microdollar columns, tool costs folded in, rollups
across the tree. Flue emits numbers and hands them to someone else&amp;rsquo;s dashboard.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authoring without the repo.&lt;/strong&gt; An interview, a generated brief, a skill your
coding agent can use.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No build step.&lt;/strong&gt; &lt;code&gt;agent serve&lt;/code&gt; and you have an API, a UI, and SQLite. Flue
needs Vite, a config, a plugin ordering rule, and hand-maintained Durable
Object migrations on Cloudflare.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An encrypted secret store&lt;/strong&gt; instead of env vars, so a key rotates without a
redeploy.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An outbound HTTP tool with an address policy.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="pick-flue-if"&gt;Pick Flue if&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The work must not be lost. A redeploy mid-task is routine and a dropped job is
an incident.&lt;/li&gt;
&lt;li&gt;The agent runs code against a real boundary. You want a disposable Linux box
with a package manager, not a capped subprocess.&lt;/li&gt;
&lt;li&gt;Users talk to it, in Slack or GitHub or a chat UI in your product.&lt;/li&gt;
&lt;li&gt;You are a TypeScript team shipping a product, and you want agents in the repo
and reviewed in pull requests.&lt;/li&gt;
&lt;li&gt;You are already on Cloudflare. Durable Objects solve the one-owner problem that
Node leaves to you.&lt;/li&gt;
&lt;li&gt;You want an OSS project with a future behind it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="pick-this-if"&gt;Pick this if&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The agents should be editable by a person in a UI, versioned, replayed, and
compared, by someone who is not going to open a repo.&lt;/li&gt;
&lt;li&gt;The orchestration patterns matter. Debate, reflection, gates, fan-in merges,
conditional routing. A config field here, a week of control flow there.&lt;/li&gt;
&lt;li&gt;You want to A/B the wording.&lt;/li&gt;
&lt;li&gt;The work is job-shaped, not conversation-shaped. Pipelines, batch
classification, a nightly report. Run history is the product.&lt;/li&gt;
&lt;li&gt;You want it running in one command, with no build and no deploy story.&lt;/li&gt;
&lt;li&gt;You want to repoint models, rotate credentials, or rewire a graph without a
redeploy.&lt;/li&gt;
&lt;li&gt;You want to own and vendor the runtime.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="bottom-line"&gt;Bottom line&lt;/h2&gt;
&lt;p&gt;Flue is built to keep work running. This is built to let you change the work and
see what changed.&lt;/p&gt;
&lt;p&gt;Someone picks Flue because their agent has to survive a redeploy while a customer
is talking to it. Someone picks this because they want to change a prompt, re-run
yesterday&amp;rsquo;s job against both versions, and see which one cost less.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id="against-sapiom"&gt;Against Sapiom&lt;/h1&gt;
&lt;p&gt;Sapiom is venture-funded and hosted. Around $50M raised, Accel-led. It has three
pieces:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Router.&lt;/strong&gt; An OpenAI-compatible endpoint that picks a model per request from a
small curated list and meters every call.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Runtime.&lt;/strong&gt; A managed engine that runs your agent in their cloud as a typed
step graph with durable pauses, retries, schedules, and per-attempt traces.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Capabilities.&lt;/strong&gt; A catalog of paid services an agent calls through one
pre-authenticated client. Web search, scraping, browser sessions, sandboxes,
Postgres provisioning, image and video generation, file storage, email lookup,
domain registration. No vendor accounts.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That third piece is the business. The pitch is that agents buy their own tools and
every API call is a payment. Sapiom is a procurement layer with a runtime around
it. This project is a runtime whose only payments layer is your OpenRouter bill.&lt;/p&gt;
&lt;h2 id="side-by-side-1"&gt;Side by side&lt;/h2&gt;
&lt;div class="agents-table-wrap"&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;This project&lt;/th&gt;
&lt;th&gt;Sapiom&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Where it runs&lt;/td&gt;
&lt;td&gt;Your machine, any host, or inside your own app as a library&lt;/td&gt;
&lt;td&gt;Their cloud only. Local runs stub every capability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Defining an agent&lt;/td&gt;
&lt;td&gt;A JSON row, editable in the UI, versioned on every save&lt;/td&gt;
&lt;td&gt;TypeScript in a repo they provision, with compile-checked step transitions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who writes it&lt;/td&gt;
&lt;td&gt;A person in the UI, an interview, or a coding agent&lt;/td&gt;
&lt;td&gt;A desktop studio, or their MCP server inside your coding agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Execution modes&lt;/td&gt;
&lt;td&gt;Freeform loop, DAG with conditions and merges, plan, debate, reflection, gates, peer messaging&lt;/td&gt;
&lt;td&gt;One step graph. Branching and escalation are hand-written&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model access&lt;/td&gt;
&lt;td&gt;Any OpenRouter model, your key, tiers as data&lt;/td&gt;
&lt;td&gt;Five curated models, their key, latency lanes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tools&lt;/td&gt;
&lt;td&gt;~22 first-party, plus yours, plus MCP. You bring the credentials&lt;/td&gt;
&lt;td&gt;A capability catalog. No credentials to bring, and no way to add your own&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sandbox&lt;/td&gt;
&lt;td&gt;Capped subprocess, no filesystem namespace&lt;/td&gt;
&lt;td&gt;Managed cloud environments and a hosted coding agent, metered per call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Durable pause&lt;/td&gt;
&lt;td&gt;Graph nodes park and resume by token, and an answered run that never restarted is picked up at boot&lt;/td&gt;
&lt;td&gt;Signals with correlation ids and deadlines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retries&lt;/td&gt;
&lt;td&gt;Gates with retry, plan re-planning. No automatic per-step retry&lt;/td&gt;
&lt;td&gt;Three attempts per step by default, plus an explicit retry directive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scheduling&lt;/td&gt;
&lt;td&gt;None. Cron the CLI&lt;/td&gt;
&lt;td&gt;Cron, events, and webhook triggers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory&lt;/td&gt;
&lt;td&gt;Durable memory with compare-and-set&lt;/td&gt;
&lt;td&gt;Not a named feature. Use file storage or a provisioned database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Your OpenRouter usage, and nothing else&lt;/td&gt;
&lt;td&gt;$0.50 to $1 per run on top of usage, plus per-call capability rates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth and tenancy&lt;/td&gt;
&lt;td&gt;None at all&lt;/td&gt;
&lt;td&gt;Accounts, orgs, per-agent spend rules, SSO, audit trail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compliance&lt;/td&gt;
&lt;td&gt;Whatever your host gives you&lt;/td&gt;
&lt;td&gt;SOC 2, SLAs, telemetry export&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lock-in&lt;/td&gt;
&lt;td&gt;Low. Agents are JSON, and the runtime vendors into your tree&lt;/td&gt;
&lt;td&gt;High. Step code imports their package and capabilities are their client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local dev&lt;/td&gt;
&lt;td&gt;The real thing runs locally. Same database, same tools, real calls&lt;/td&gt;
&lt;td&gt;Real step code, stubbed capabilities. Production behavior first happens in their cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h2 id="what-sapiom-does-better"&gt;What Sapiom does better&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Someone else holds every credential and every invoice.&lt;/strong&gt; A Sapiom agent can
spin up a browser, register a domain, provision a Postgres, and generate a
video with nobody creating an account anywhere. Here each of those is a key you
go get. That gap got smaller when the fetch tool landed, since an HTTP API
with a key is now a tool definition rather than a module. The signups and the
invoices are untouched, and they were always the point.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud sandboxes and a hosted coding agent.&lt;/strong&gt; Managed compute with a real
filesystem boundary and a package manager.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Schedules and webhook triggers.&lt;/strong&gt; Here you wire cron to the CLI yourself.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auth, tenants, policies, spend caps.&lt;/strong&gt; None of that exists here. This
platform has no notion of a caller anywhere.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compile-time graph checking.&lt;/strong&gt; Undeclared transitions are type errors. The
equivalent here is schema validation at save time, which is good but
runtime-typed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Model routing by task shape.&lt;/strong&gt; Here a tier is a fixed pointer you set.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Support, SLAs, SOC 2.&lt;/strong&gt; You pay for someone to be on the hook.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="what-this-project-does-better-1"&gt;What this project does better&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;It runs anywhere.&lt;/strong&gt; One process, SQLite, no account. Sapiom cannot run your
agent outside their cloud, and even local authoring needs a sign-in.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agents as data.&lt;/strong&gt; Version history, a UI, replay. Their agents are TypeScript,
so changing a prompt is a deploy.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Higher-level orchestration built in.&lt;/strong&gt; Debate, reflection, plan review, merge
strategies, conditional edges, gates. You hand-write all of that as steps there.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A freeform loop as a first-class mode.&lt;/strong&gt; Their unit is a deterministic step.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bring your own model provider.&lt;/strong&gt; Any OpenRouter model, any price, your key.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bring your own tools, at no disadvantage.&lt;/strong&gt; A file in &lt;code&gt;./tools&lt;/code&gt; gets the same
treatment a built-in gets. Sapiom has no user-defined capability mechanism at
all. Anything outside the catalog is a &lt;code&gt;fetch&lt;/code&gt; buried in step code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Peer messaging&lt;/strong&gt; between concurrent nodes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Durable memory with compare-and-set.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tool definitions as data.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No per-run fee, and this is the big practical one.&lt;/strong&gt; A run on a cheap model
here costs about five cents. A heavy day of $10 in model calls is roughly 200
runs, which on Sapiom is $100 to $200 in run fees before any usage or
capability rates. Ten to twenty times the cost is not a rounding error.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Full local fidelity.&lt;/strong&gt; Your test run is the production run.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Source you can read, vendor, or depend on outright.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="pick-sapiom-if"&gt;Pick Sapiom if&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;You do not want to collect API keys. Search, a headless browser, a scratch
database, image generation, email lookup. That is five signups and five invoices
on your own host, and one bill on theirs. This is the whole pitch and it is real.&lt;/li&gt;
&lt;li&gt;You need a sandbox strong enough to be somebody else&amp;rsquo;s problem.&lt;/li&gt;
&lt;li&gt;You need schedules and inbound webhooks without building them.&lt;/li&gt;
&lt;li&gt;You are a team, not a person. Spend limits, an audit trail, SSO, someone to call.&lt;/li&gt;
&lt;li&gt;You want compliance paper.&lt;/li&gt;
&lt;li&gt;You prefer typed code over JSON config, with transitions checked by a compiler
and agents reviewed in pull requests.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="pick-this-if-1"&gt;Pick this if&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;It is a personal or small-scale tool. No per-run fee, no account, no vendor.&lt;/li&gt;
&lt;li&gt;The agents need to be editable in a UI, versioned, replayed, and compared.&lt;/li&gt;
&lt;li&gt;The orchestration patterns matter.&lt;/li&gt;
&lt;li&gt;You want to own the runtime. Read it, vendor it, patch it, run it on a Raspberry
Pi, or compile it into your own app.&lt;/li&gt;
&lt;li&gt;You already have tools or MCP servers and want them treated as first-class.&lt;/li&gt;
&lt;li&gt;You want any model, including ones Sapiom does not route to.&lt;/li&gt;
&lt;li&gt;You do not want your agent logic and all of its data flowing through someone
else&amp;rsquo;s cloud.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="bottom-line-1"&gt;Bottom line&lt;/h2&gt;
&lt;p&gt;They overlap on the runtime. Durable pause and resume, step graphs, shared state,
traces, secrets, cost tracking. This project is ahead on orchestration patterns and
on agents-as-data. Sapiom is ahead on everything around the runtime: hosting,
scheduling, auth, tenancy, compliance, and a catalog of prepaid services.&lt;/p&gt;
&lt;p&gt;What Sapiom sells is not having to hold the key. Someone picks Sapiom because they
want to not operate anything and not sign up for anything. Someone picks this
because they want to own it and run it cheaply.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="what-i-would-have-to-build-to-close-the-gaps"&gt;What I would have to build to close the gaps&lt;/h2&gt;
&lt;p&gt;No hosting or billing in this list, because that is not a thing I am going to
compete on.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Freeform durability.&lt;/strong&gt; The biggest one and the hardest. A graph parks durably
and a freeform run does not, because the freeform loop&amp;rsquo;s state lives inside the
AI SDK&amp;rsquo;s multi-step generation. Closing it means owning the turn loop. Large,
and arguably a rewrite of the runner.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nested parking.&lt;/strong&gt; Sub-agent nodes, reflection producers, debate producers, and
peer waves cannot park today. Medium-large, and it unblocks the rest.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Recovery, not just reaping.&lt;/strong&gt; Attempts, leases, and a classified resume, so a
dead run continues instead of being closed out. Medium. The easy piece is done:
a run that was answered and never restarted gets picked up. What is left is the
hard half, continuing a run that died in the middle of a step.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Durable tools.&lt;/strong&gt; A checkpoint so a tool that provisions something resumes
mid-way. Small-medium on top of parking.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A stronger sandbox.&lt;/strong&gt; A filesystem namespace, seccomp, a cgroup, installable
packages. On reflection this is better bought than built, as an adapter for E2B
or Daytona. Medium-large, and no longer blocking ordinary compute.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Channels.&lt;/strong&gt; Verified webhook ingress with signature checking and idempotency,
then a route that starts a run. The first provider is most of the work.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Schedules.&lt;/strong&gt; A cron table plus a route that starts a run from a POST.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Skills.&lt;/strong&gt; The agentskills.io format is open and already has adopters.
Progressive disclosure is a catalog line plus a read tool. Cheap, and it
interoperates.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A thin client SDK.&lt;/strong&gt; Small. The CLI already has every call.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Context compaction.&lt;/strong&gt; Nothing here manages a growing conversation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenTelemetry export.&lt;/strong&gt; The run tree is good, but a team with a tracing backend
wants spans.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Evals.&lt;/strong&gt; Replay and comparison are halfway there.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auth and tenants.&lt;/strong&gt; A bearer token is small. A real caller identity on every
aggregate is medium-large, and every query grows a filter.&lt;/li&gt;
&lt;/ul&gt;
&lt;nav class="agents-pager"&gt;
&lt;div class="prev"&gt;&lt;span class="label"&gt;Previous&lt;/span&gt; &lt;a href="https://khtdr.com/agents/running"&gt;Operating it&lt;/a&gt;&lt;/div&gt;
&lt;div class="next"&gt;&lt;span class="label"&gt;Back to the&lt;/span&gt; &lt;a href="https://khtdr.com/agents"&gt;overview&lt;/a&gt;&lt;/div&gt;
&lt;/nav&gt;
]]&gt;</content:encoded></item><item><title>agents: CLI, API, embedding</title><description> Overview · Getting started · Features · Tools · Running code · CLI, API, embedding · Operating it · How it compares The web UI is a client, not the product. Everything it does is an HTTP call, the CLI makes the same calls, and the engine underneath both is a library you can import. Pick whichever one fits what you are doing. The CLI It streams output, takes piped input and file attachments, and prompts you right there when a run stops to ask something.</description><link>https://khtdr.com/agents/cli-api/</link><guid>https://khtdr.com/agents/cli-api/</guid><pubDate>Tue, 15 Sep 2026 00:00:00 -0600</pubDate><author>mazzarelli@gmail.com (Joey Mazzarelli)</author><content:encoded>
&lt;![CDATA[
&lt;link rel="stylesheet" href="https://khtdr.com/css/agents.css" /&gt;
&lt;nav class="agents-nav"&gt;&lt;a href="https://khtdr.com/agents"&gt;Overview&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/start"&gt;Getting started&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/features"&gt;Features&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/tools"&gt;Tools&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/sandbox"&gt;Running code&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;span class="here"&gt;CLI, API, embedding&lt;/span&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/running"&gt;Operating it&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/compare"&gt;How it compares&lt;/a&gt;&lt;/nav&gt;
&lt;p class="agents-hero"&gt;
The web UI is a client, not the product. Everything it does is an HTTP call, the
CLI makes the same calls, and the engine underneath both is a library you can
import. Pick whichever one fits what you are doing.
&lt;/p&gt;
&lt;h2 id="the-cli"&gt;The CLI&lt;/h2&gt;
&lt;p&gt;It streams output, takes piped input and file attachments, and prompts you right
there when a run stops to ask something.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;agent researcher &lt;span class="s2"&gt;&amp;#34;what changed in HTTP/3 this year&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;summarize this&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; agent summarizer -f notes.md
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;agent new &lt;span class="c1"&gt;# build an agent by answering questions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;agent skill install &lt;span class="c1"&gt;# teach Claude Code to write agents here&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;agent brief &lt;span class="c1"&gt;# what this install can do, as one document&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;agent tools &lt;span class="c1"&gt;# every tool an agent can use, and where each came from&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;agent pending&lt;/code&gt; is the inbox of everything waiting on a person:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;agent pending
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# Waiting (1)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# support-router aa3793db · 19h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# ⏸ Which environments should this go to?
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="the-http-api"&gt;The HTTP API&lt;/h2&gt;
&lt;p&gt;It is the same API the UI uses. About forty routes covering agents, runs, tools,
secrets, memory, models, files, and the brief. Anything you can click, you can
script.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -X POST localhost:2137/api/agents/researcher/run &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;content-type: application/json&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -d &lt;span class="s1"&gt;&amp;#39;{&amp;#34;input&amp;#34;: &amp;#34;what changed in HTTP/3 this year&amp;#34;}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl localhost:2137/api/brief &lt;span class="c1"&gt;# what this install can author&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl localhost:2137/api/runs/pending &lt;span class="c1"&gt;# every run waiting on a person&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -X POST localhost:2137/api/runs/&lt;span class="nv"&gt;$ID&lt;/span&gt;/pending/&lt;span class="nv"&gt;$TOKEN&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -d &lt;span class="s1"&gt;&amp;#39;{&amp;#34;response&amp;#34;: {&amp;#34;answer&amp;#34;: [&amp;#34;staging&amp;#34;]}}&amp;#39;&lt;/span&gt; &lt;span class="c1"&gt;# and answering one&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There is no authentication on it. That is why it binds to &lt;code&gt;127.0.0.1&lt;/code&gt; by default.
See &lt;a href="https://khtdr.com/agents/running"&gt;operating it&lt;/a&gt; before you expose it.&lt;/p&gt;
&lt;h2 id="embedding-it-in-your-own-app"&gt;Embedding it in your own app&lt;/h2&gt;
&lt;p&gt;The runtime that &lt;code&gt;agent serve&lt;/code&gt; wraps is also a library. Your code calls &lt;code&gt;runAgent&lt;/code&gt;
as a function, the runs land in a database you choose, and there is no HTTP in
between. Your app keeps its own login and its own users. The agents become a
feature of it.&lt;/p&gt;
&lt;p&gt;From your project&amp;rsquo;s directory, copy the runtime in and install it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;agent bundle &lt;span class="c1"&gt;# writes lib/agents&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm install ./lib/agents ai zod
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then a script like this runs an agent and reads the result back:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createRuntime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createAgent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;runAgent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getStore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toolRegistry&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@upship/agent-core&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ai&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;zod&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;toolRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;clock&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Report the current time. Use it when asked what time it is.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;The current time as ISO 8601.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parameters&lt;/span&gt;: &lt;span class="kt"&gt;z.object&lt;/span&gt;&lt;span class="p"&gt;({}),&lt;/span&gt; &lt;span class="nx"&gt;execute&lt;/span&gt;: &lt;span class="kt"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;: &lt;span class="kt"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;createRuntime&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;timers&lt;/span&gt;: &lt;span class="kt"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;getStore&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;getAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;timekeeper&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;createAgent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;timekeeper&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Tells the time&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;systemPrompt&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Answer using the clock tool.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;clock&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;runAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;What time is it?&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;getStore&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;getRun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Set &lt;code&gt;OPENROUTER_API_KEY&lt;/code&gt; and &lt;code&gt;STORE_DRIVER=sqlite&lt;/code&gt;, then run it with
&lt;code&gt;node example.ts&lt;/code&gt; on Node 24 or later.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;createRuntime&lt;/code&gt; does what the server does at startup. It opens the store, sets up
tools and models, and starts the cleanup timers. A script that runs once turns the
timers off. A server that stays up leaves them on and calls &lt;code&gt;rt.stop()&lt;/code&gt; on
shutdown.&lt;/p&gt;
&lt;p&gt;Every feature works the same way here. Graphs, gates, memory, secrets and cost
tracking all live in the runtime, not in the server. The one part that does not
come along is the web UI, which is a static page over the HTTP API. Read runs out
of the store and show them your own way.&lt;/p&gt;
&lt;p&gt;To upgrade, run &lt;code&gt;agent bundle&lt;/code&gt; again after updating the CLI. The Embedding page in
the web UI walks through this example a line at a time and says what each one
commits you to.&lt;/p&gt;
&lt;nav class="agents-pager"&gt;
&lt;div class="prev"&gt;&lt;span class="label"&gt;Previous&lt;/span&gt; &lt;a href="https://khtdr.com/agents/sandbox"&gt;Running code&lt;/a&gt;&lt;/div&gt;
&lt;div class="next"&gt;&lt;span class="label"&gt;Next&lt;/span&gt; &lt;a href="https://khtdr.com/agents/running"&gt;Operating it&lt;/a&gt;&lt;/div&gt;
&lt;/nav&gt;
]]&gt;</content:encoded></item><item><title>agents: features</title><description> Overview · Getting started · Features · Tools · Running code · CLI, API, embedding · Operating it · How it compares Authoring and versioning Feature What it does Why you want it Agents as rows Name, prompt, tier, tools, and an optional pipeline. Edit from the browser, the CLI, or the API Change a prompt or add a step without a deploy Built-in authoring agent new interviews you and drafts an agent. Or install a skill so your coding agent can Go from install to a working agent without writing JSON Versions and replay Every save keeps a version. Any run can be replayed against another version or different tools A/B test a prompt or a tool description on the same input Swap models globally Agents ask for fast, default, or reasoning. You decide what those point at Move every agent to a new or cheaper model in one place Cost tracking Tokens and tool spend down to the microdollar, added up across the whole run See what a pipeline cost, not just the first call Pipelines Feature What it does Why you want it Agents as tools An agent can list other agents alongside its tools Build pipelines out of agents you already have Pipelines (DAG) Ordered steps, explicit connections, parallel branches, merges Fixed sequences are cheaper and faster, and a failure points at one step Visual composer Drag to connect, click to edit, validation as you type Build and fix a pipeline without hand-editing config Conditional routing A connection can carry a condition on the previous step&rsquo;s output One classifier, four handlers, only one branch runs Merges Join branches by concatenating, listing, summarizing, or picking the best Fan out to several agents and get one answer back Gates A step that checks the work and either passes it on or sends it back Retry the step that produced bad output, with the complaint attached Checked at save time Loops, missing steps, and conditions that do not parse are rejected on save The pipeline fails in the editor, not three steps into a run Getting a better answer Feature What it does Why you want it Reflection loops Draft, critique, revise, until a critic signs off Quality passes that do not need you in the loop Quality gate A critic reviews the finished output and flags the run if it is not good Catch bad answers without anything silently rewriting them Debate and consensus Run it N times or ask a panel, then pick by vote, weight, or judge Better answers on questions where one sample is a coin flip Plan then execute The model writes the pipeline before anything runs, and you can approve it first Review the plan while it is still free to change Guaranteed JSON Put a schema on an agent and its output is validated against it Route on a field, or hand the value to the next step as data People in the loop Feature What it does Why you want it Human in the loop A step can stop and ask a person, with typed options Approvals and sign-offs inside an otherwise automatic run Multi-turn interviews A conversation where each question depends on the last answer Gather requirements from a person mid-run Durable pauses A paused run keeps its place with no process waiting on it, and picks itself back up if the server restarts while the answer is coming in A webhook, a build, or a person can answer hours later, and a deploy in the middle of it does not lose the run A pending inbox One list of every run waiting on somebody, in the UI and the CLI Nothing sits parked because you forgot about it State and memory Feature What it does Why you want it Shared scratchpad Steps read and write a shared key/value store for the run Parallel branches contribute to one result without threading it through every step Long-term memory Namespaced values that outlive the run, safe when two runs race Jobs that pick up where the last run stopped Agents that talk Agents running in parallel can message each other Negotiation and critique between siblings, with stalls detected Tools and integration Feature What it does Why you want it Tool config and A/B Tool descriptions and settings are editable data on top of the code Test two wordings of a tool, or point two copies at different accounts Encrypted secrets Credentials are stored encrypted and referenced by name Point a tool at your account without pasting a key into config Call any HTTP API A step can fetch a URL, or be pinned to one API with the key stored encrypted and hidden from the model Connect to a service you already pay for without writing a tool Run code it wrote A step can write Node, Python, or shell and run it in a capped subprocess with none of your keys in it Arithmetic, parsing, and charts get computed instead of guessed at Filesystem limits Tools get scoped paths instead of raw disk access A run writes in its own directory and cannot reach outside it Bring your own tools Drop a file in a tools folder, install a tool package, or connect an MCP server Add a tool without forking or rebuilding anything Operations Feature What it does Why you want it Storage that fits SQLite, Postgres, plain files, or memory Start on a laptop, move to a real database when it matters Nothing gets stuck Runs check in, dead ones get cleaned up, answered ones that never got restarted are picked back up, and cancel stops the run No run sits waiting forever because something crashed at the wrong moment Web UI, CLI, API One engine, three ways to use it Click through it, script it, or call it from your own app Embed the runtime agent bundle puts the runtime in your tree and runAgent becomes a function call Ship agents inside your product with no separate service to run The control panel An agent: its prompt, model tier, step limit, and the pipeline that says what it does. Every save keeps a version, and each run links back to the version that produced it. Run history, like a git log. Sub-agent calls nest under the run that made them, each with its own tokens, cost, and time. A pipeline's total is the sum of its parts. One run in full: what it cost, what it called, and what each part said. When a run needs a person it pauses and waits. No process is held open. The form is built from the question, so a step that offers three choices cannot get a fourth. Agents ask for fast, default, or reasoning. You decide what those point at, prices included. Every agent picks up the change on its next run. Tools are code, but their descriptions and settings are editable data on top. Two entries can share one implementation with different wording or different accounts. What agents wrote down to remember between runs. A watermark, a last-seen id, a decision that should not get made twice. Runs, cost, tokens, average duration, and recent errors for the last 24 hours. Under the hood TypeScript throughout. A small HTTP server, a React UI, and a runner you can embed in your own app. One key, every provider. Models come through OpenRouter. Switching from Claude to GPT to Gemini is a setting, not an integration. Laptop or cluster. SQLite by default with nothing to install. Postgres when you need more than one machine. Every storage option passes the same test suite, so behavior does not change with the backend. Conditions are parsed, not evaluated. The expression language in routing conditions is a fixed whitelist. Nothing arriving over the API can reach past it. Your tools are not second class. A tool you write in the tools folder gets the same schema handling, the same validation, the same catalog entry, and the same settings-on-top-of-code as one that shipped. Previous Getting started Next Tools</description><link>https://khtdr.com/agents/features/</link><guid>https://khtdr.com/agents/features/</guid><pubDate>Tue, 15 Sep 2026 00:00:00 -0600</pubDate><author>mazzarelli@gmail.com (Joey Mazzarelli)</author><content:encoded>
&lt;![CDATA[
&lt;link rel="stylesheet" href="https://khtdr.com/css/agents.css" /&gt;
&lt;nav class="agents-nav"&gt;&lt;a href="https://khtdr.com/agents"&gt;Overview&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/start"&gt;Getting started&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;span class="here"&gt;Features&lt;/span&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/tools"&gt;Tools&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/sandbox"&gt;Running code&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/cli-api"&gt;CLI, API, embedding&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/running"&gt;Operating it&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/compare"&gt;How it compares&lt;/a&gt;&lt;/nav&gt;
&lt;h2 id="authoring-and-versioning"&gt;Authoring and versioning&lt;/h2&gt;
&lt;div class="agents-table-wrap"&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Why you want it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Agents as rows&lt;/td&gt;
&lt;td&gt;Name, prompt, tier, tools, and an optional pipeline. Edit from the browser, the CLI, or the API&lt;/td&gt;
&lt;td&gt;Change a prompt or add a step without a deploy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Built-in authoring&lt;/td&gt;
&lt;td&gt;&lt;code&gt;agent new&lt;/code&gt; interviews you and drafts an agent. Or install a skill so your coding agent can&lt;/td&gt;
&lt;td&gt;Go from install to a working agent without writing JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Versions and replay&lt;/td&gt;
&lt;td&gt;Every save keeps a version. Any run can be replayed against another version or different tools&lt;/td&gt;
&lt;td&gt;A/B test a prompt or a tool description on the same input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Swap models globally&lt;/td&gt;
&lt;td&gt;Agents ask for &lt;code&gt;fast&lt;/code&gt;, &lt;code&gt;default&lt;/code&gt;, or &lt;code&gt;reasoning&lt;/code&gt;. You decide what those point at&lt;/td&gt;
&lt;td&gt;Move every agent to a new or cheaper model in one place&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost tracking&lt;/td&gt;
&lt;td&gt;Tokens and tool spend down to the microdollar, added up across the whole run&lt;/td&gt;
&lt;td&gt;See what a pipeline cost, not just the first call&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h2 id="pipelines"&gt;Pipelines&lt;/h2&gt;
&lt;div class="agents-table-wrap"&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Why you want it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Agents as tools&lt;/td&gt;
&lt;td&gt;An agent can list other agents alongside its tools&lt;/td&gt;
&lt;td&gt;Build pipelines out of agents you already have&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pipelines (DAG)&lt;/td&gt;
&lt;td&gt;Ordered steps, explicit connections, parallel branches, merges&lt;/td&gt;
&lt;td&gt;Fixed sequences are cheaper and faster, and a failure points at one step&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visual composer&lt;/td&gt;
&lt;td&gt;Drag to connect, click to edit, validation as you type&lt;/td&gt;
&lt;td&gt;Build and fix a pipeline without hand-editing config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conditional routing&lt;/td&gt;
&lt;td&gt;A connection can carry a condition on the previous step&amp;rsquo;s output&lt;/td&gt;
&lt;td&gt;One classifier, four handlers, only one branch runs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Merges&lt;/td&gt;
&lt;td&gt;Join branches by concatenating, listing, summarizing, or picking the best&lt;/td&gt;
&lt;td&gt;Fan out to several agents and get one answer back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gates&lt;/td&gt;
&lt;td&gt;A step that checks the work and either passes it on or sends it back&lt;/td&gt;
&lt;td&gt;Retry the step that produced bad output, with the complaint attached&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Checked at save time&lt;/td&gt;
&lt;td&gt;Loops, missing steps, and conditions that do not parse are rejected on save&lt;/td&gt;
&lt;td&gt;The pipeline fails in the editor, not three steps into a run&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h2 id="getting-a-better-answer"&gt;Getting a better answer&lt;/h2&gt;
&lt;div class="agents-table-wrap"&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Why you want it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Reflection loops&lt;/td&gt;
&lt;td&gt;Draft, critique, revise, until a critic signs off&lt;/td&gt;
&lt;td&gt;Quality passes that do not need you in the loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quality gate&lt;/td&gt;
&lt;td&gt;A critic reviews the finished output and flags the run if it is not good&lt;/td&gt;
&lt;td&gt;Catch bad answers without anything silently rewriting them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debate and consensus&lt;/td&gt;
&lt;td&gt;Run it N times or ask a panel, then pick by vote, weight, or judge&lt;/td&gt;
&lt;td&gt;Better answers on questions where one sample is a coin flip&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plan then execute&lt;/td&gt;
&lt;td&gt;The model writes the pipeline before anything runs, and you can approve it first&lt;/td&gt;
&lt;td&gt;Review the plan while it is still free to change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Guaranteed JSON&lt;/td&gt;
&lt;td&gt;Put a schema on an agent and its output is validated against it&lt;/td&gt;
&lt;td&gt;Route on a field, or hand the value to the next step as data&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h2 id="people-in-the-loop"&gt;People in the loop&lt;/h2&gt;
&lt;div class="agents-table-wrap"&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Why you want it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Human in the loop&lt;/td&gt;
&lt;td&gt;A step can stop and ask a person, with typed options&lt;/td&gt;
&lt;td&gt;Approvals and sign-offs inside an otherwise automatic run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-turn interviews&lt;/td&gt;
&lt;td&gt;A conversation where each question depends on the last answer&lt;/td&gt;
&lt;td&gt;Gather requirements from a person mid-run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Durable pauses&lt;/td&gt;
&lt;td&gt;A paused run keeps its place with no process waiting on it, and picks itself back up if the server restarts while the answer is coming in&lt;/td&gt;
&lt;td&gt;A webhook, a build, or a person can answer hours later, and a deploy in the middle of it does not lose the run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A pending inbox&lt;/td&gt;
&lt;td&gt;One list of every run waiting on somebody, in the UI and the CLI&lt;/td&gt;
&lt;td&gt;Nothing sits parked because you forgot about it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h2 id="state-and-memory"&gt;State and memory&lt;/h2&gt;
&lt;div class="agents-table-wrap"&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Why you want it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Shared scratchpad&lt;/td&gt;
&lt;td&gt;Steps read and write a shared key/value store for the run&lt;/td&gt;
&lt;td&gt;Parallel branches contribute to one result without threading it through every step&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long-term memory&lt;/td&gt;
&lt;td&gt;Namespaced values that outlive the run, safe when two runs race&lt;/td&gt;
&lt;td&gt;Jobs that pick up where the last run stopped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agents that talk&lt;/td&gt;
&lt;td&gt;Agents running in parallel can message each other&lt;/td&gt;
&lt;td&gt;Negotiation and critique between siblings, with stalls detected&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h2 id="tools-and-integration"&gt;Tools and integration&lt;/h2&gt;
&lt;div class="agents-table-wrap"&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Why you want it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tool config and A/B&lt;/td&gt;
&lt;td&gt;Tool descriptions and settings are editable data on top of the code&lt;/td&gt;
&lt;td&gt;Test two wordings of a tool, or point two copies at different accounts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encrypted secrets&lt;/td&gt;
&lt;td&gt;Credentials are stored encrypted and referenced by name&lt;/td&gt;
&lt;td&gt;Point a tool at your account without pasting a key into config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Call any HTTP API&lt;/td&gt;
&lt;td&gt;A step can fetch a URL, or be pinned to one API with the key stored encrypted and hidden from the model&lt;/td&gt;
&lt;td&gt;Connect to a service you already pay for without writing a tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run code it wrote&lt;/td&gt;
&lt;td&gt;A step can write Node, Python, or shell and run it in a capped subprocess with none of your keys in it&lt;/td&gt;
&lt;td&gt;Arithmetic, parsing, and charts get computed instead of guessed at&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filesystem limits&lt;/td&gt;
&lt;td&gt;Tools get scoped paths instead of raw disk access&lt;/td&gt;
&lt;td&gt;A run writes in its own directory and cannot reach outside it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bring your own tools&lt;/td&gt;
&lt;td&gt;Drop a file in a &lt;code&gt;tools&lt;/code&gt; folder, install a tool package, or connect an MCP server&lt;/td&gt;
&lt;td&gt;Add a tool without forking or rebuilding anything&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h2 id="operations"&gt;Operations&lt;/h2&gt;
&lt;div class="agents-table-wrap"&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Why you want it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Storage that fits&lt;/td&gt;
&lt;td&gt;SQLite, Postgres, plain files, or memory&lt;/td&gt;
&lt;td&gt;Start on a laptop, move to a real database when it matters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nothing gets stuck&lt;/td&gt;
&lt;td&gt;Runs check in, dead ones get cleaned up, answered ones that never got restarted are picked back up, and cancel stops the run&lt;/td&gt;
&lt;td&gt;No run sits waiting forever because something crashed at the wrong moment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web UI, CLI, API&lt;/td&gt;
&lt;td&gt;One engine, three ways to use it&lt;/td&gt;
&lt;td&gt;Click through it, script it, or call it from your own app&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Embed the runtime&lt;/td&gt;
&lt;td&gt;&lt;code&gt;agent bundle&lt;/code&gt; puts the runtime in your tree and &lt;code&gt;runAgent&lt;/code&gt; becomes a function call&lt;/td&gt;
&lt;td&gt;Ship agents inside your product with no separate service to run&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h2 id="the-control-panel"&gt;The control panel&lt;/h2&gt;
&lt;figure class="agents-shot"&gt;
&lt;img src="https://khtdr.com/img/agents/agent-detail.jpg" alt="An agent detail page showing the system prompt, model tier, a rendered graph pipeline, and version history" /&gt;
&lt;figcaption&gt;An agent: its prompt, model tier, step limit, and the pipeline that says what it does. Every save keeps a version, and each run links back to the version that produced it.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class="agents-shot"&gt;
&lt;img src="https://khtdr.com/img/agents/run-history.jpg" alt="Run history as a collapsible tree, with sub-agent runs nested under their parents and per-run tokens, cost, and duration" /&gt;
&lt;figcaption&gt;Run history, like a git log. Sub-agent calls nest under the run that made them, each with its own tokens, cost, and time. A pipeline's total is the sum of its parts.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class="agents-shot"&gt;
&lt;img src="https://khtdr.com/img/agents/run-detail.jpg" alt="A single run's detail page with duration, steps, model, tokens, cost, and a pipeline diagram of its child runs" /&gt;
&lt;figcaption&gt;One run in full: what it cost, what it called, and what each part said.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class="agents-shot"&gt;
&lt;img src="https://khtdr.com/img/agents/pending-gate.jpg" alt="A paused run showing a 'waiting on you' panel with a question and checkboxes for staging, prod-eu and prod-us" /&gt;
&lt;figcaption&gt;When a run needs a person it pauses and waits. No process is held open. The form is built from the question, so a step that offers three choices cannot get a fourth.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class="agents-shot"&gt;
&lt;img src="https://khtdr.com/img/agents/models.jpg" alt="The models settings page, binding the default, fast and reasoning tiers to model ids with per-million-token prices" /&gt;
&lt;figcaption&gt;Agents ask for &lt;code&gt;fast&lt;/code&gt;, &lt;code&gt;default&lt;/code&gt;, or &lt;code&gt;reasoning&lt;/code&gt;. You decide what those point at, prices included. Every agent picks up the change on its next run.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class="agents-shot"&gt;
&lt;img src="https://khtdr.com/img/agents/tools.jpg" alt="The tools page listing versioned tool definitions above the code implementations they point at" /&gt;
&lt;figcaption&gt;Tools are code, but their descriptions and settings are editable data on top. Two entries can share one implementation with different wording or different accounts.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class="agents-shot"&gt;
&lt;img src="https://khtdr.com/img/agents/memory.jpg" alt="The memory page showing namespaced durable keys with their values and when they were last written" /&gt;
&lt;figcaption&gt;What agents wrote down to remember between runs. A watermark, a last-seen id, a decision that should not get made twice.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class="agents-shot"&gt;
&lt;img src="https://khtdr.com/img/agents/dashboard.jpg" alt="The dashboard showing 24-hour run counts, cost, tokens, average duration, recent errors and recent activity" /&gt;
&lt;figcaption&gt;Runs, cost, tokens, average duration, and recent errors for the last 24 hours.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="under-the-hood"&gt;Under the hood&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TypeScript throughout.&lt;/strong&gt; A small HTTP server, a React UI, and a runner you can embed in your own app.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One key, every provider.&lt;/strong&gt; Models come through OpenRouter. Switching from Claude to GPT to Gemini is a setting, not an integration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Laptop or cluster.&lt;/strong&gt; SQLite by default with nothing to install. Postgres when you need more than one machine. Every storage option passes the same test suite, so behavior does not change with the backend.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Conditions are parsed, not evaluated.&lt;/strong&gt; The expression language in routing conditions is a fixed whitelist. Nothing arriving over the API can reach past it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Your tools are not second class.&lt;/strong&gt; A tool you write in the &lt;code&gt;tools&lt;/code&gt; folder gets the same schema handling, the same validation, the same catalog entry, and the same settings-on-top-of-code as one that shipped.&lt;/li&gt;
&lt;/ul&gt;
&lt;nav class="agents-pager"&gt;
&lt;div class="prev"&gt;&lt;span class="label"&gt;Previous&lt;/span&gt; &lt;a href="https://khtdr.com/agents/start"&gt;Getting started&lt;/a&gt;&lt;/div&gt;
&lt;div class="next"&gt;&lt;span class="label"&gt;Next&lt;/span&gt; &lt;a href="https://khtdr.com/agents/tools"&gt;Tools&lt;/a&gt;&lt;/div&gt;
&lt;/nav&gt;
]]&gt;</content:encoded></item><item><title>agents: getting started</title><description> Overview · Getting started · Features · Tools · Running code · CLI, API, embedding · Operating it · How it compares You need one API key and one npm install. There is no database to set up, no queue, and no broker. About five minutes from nothing to a working agent. Get a model key Sign up at openrouter.ai and copy an API key. One key covers every provider, so Anthropic, OpenAI, Google and the rest all work from it. It is the only credential you need.</description><link>https://khtdr.com/agents/start/</link><guid>https://khtdr.com/agents/start/</guid><pubDate>Tue, 15 Sep 2026 00:00:00 -0600</pubDate><author>mazzarelli@gmail.com (Joey Mazzarelli)</author><content:encoded>
&lt;![CDATA[
&lt;link rel="stylesheet" href="https://khtdr.com/css/agents.css" /&gt;
&lt;nav class="agents-nav"&gt;&lt;a href="https://khtdr.com/agents"&gt;Overview&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;span class="here"&gt;Getting started&lt;/span&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/features"&gt;Features&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/tools"&gt;Tools&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/sandbox"&gt;Running code&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/cli-api"&gt;CLI, API, embedding&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/running"&gt;Operating it&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/compare"&gt;How it compares&lt;/a&gt;&lt;/nav&gt;
&lt;p class="agents-hero"&gt;
You need one API key and one npm install. There is no database to set up, no
queue, and no broker. About five minutes from nothing to a working agent.
&lt;/p&gt;
&lt;ol class="agents-steps"&gt;
&lt;li&gt;
&lt;h4&gt;Get a model key&lt;/h4&gt;
&lt;p&gt;Sign up at &lt;a href="https://openrouter.ai"&gt;openrouter.ai&lt;/a&gt; and copy an API key.
One key covers every provider, so Anthropic, OpenAI, Google and the rest all work
from it. It is the only credential you need.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h4&gt;Install it&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm install -g @upship/agents
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;agent serve
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Paste the key in when it asks. That starts the API and the web UI on
&lt;code&gt;localhost:2137&lt;/code&gt;. Storage is a SQLite file under &lt;code&gt;~/.agents&lt;/code&gt;.
You can point it at Postgres later if you outgrow that.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h4&gt;Make your first agent&lt;/h4&gt;
&lt;p&gt;Two ways, and neither needs a copy of the source. The first is to let the
platform interview you. Open a second terminal, since the server is running in the
first:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;agent new
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It asks what you want to build, follows up on the vague parts, then writes a
draft and shows it to you:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;What do you want to build?
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(take as many lines as you like — blank line when you&amp;#39;re done)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; something that watches my RSS feeds and summarizes new posts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;draft
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Name: rss-summarizer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; What: Summarizes new posts across a list of feeds.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Model: default
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Tools: web_search, write_file
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; System prompt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; You are handed a list of feed URLs. Fetch what is new since the
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; last run, and write one short paragraph per post: what it says,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; not that it exists.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Notes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Give it a durable memory namespace if you want it to remember
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; where it stopped between runs.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Create it? [y/N]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Nothing is saved until you say yes.&lt;/p&gt;
&lt;p&gt;The second way is to hand the job to the coding agent you already use:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;agent skill install &lt;span class="c1"&gt;# writes the /new-agent skill&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;claude &lt;span class="c1"&gt;# then: /new-agent&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Both read the same brief, and that brief is generated from your install. It
lists the tools you have, including ones you wrote and ones that came
from a package or an MCP server, and it explains how to add one when nothing fits.
Run &lt;code&gt;agent brief&lt;/code&gt; to print it if you want it somewhere else.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h4&gt;Run it&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;agent rss-summarizer &lt;span class="s2"&gt;&amp;#34;catch me up on this week&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Or open the web UI and click Run. Either way the run lands in history with its
cost, its steps, and every sub-agent it called.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="what-you-just-installed"&gt;What you just installed&lt;/h2&gt;
&lt;p&gt;Everything lives under &lt;code&gt;~/.agents&lt;/code&gt;. That directory holds the &lt;code&gt;.env&lt;/code&gt; with your key
and settings, the SQLite database, and the folders tools read from and write to.
Back up that one directory and you have backed up the install. Set &lt;code&gt;AGENTS_HOME&lt;/code&gt;
to put it somewhere else.&lt;/p&gt;
&lt;p&gt;The server is a single Node process. It serves the HTTP API, the web UI, and
nothing else. The CLI talks to it over that same API, so anything
the CLI does you can also do over HTTP.&lt;/p&gt;
&lt;h2 id="where-to-go-next"&gt;Where to go next&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The agent you just made runs a freeform loop: the model picks tools until it is
done. If you want fixed steps, branches, or a human approval in the middle, that
is a &lt;a href="https://khtdr.com/agents/features"&gt;pipeline&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;If you want it to reach one of your own services, look at
&lt;a href="https://khtdr.com/agents/tools"&gt;tools&lt;/a&gt; before you write any code. An HTTP API with a key is
usually a config change, not a module.&lt;/li&gt;
&lt;li&gt;If you want it to compute rather than estimate, give it
&lt;a href="https://khtdr.com/agents/sandbox"&gt;run_script&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;nav class="agents-pager"&gt;
&lt;div class="prev"&gt;&lt;span class="label"&gt;Previous&lt;/span&gt; &lt;a href="https://khtdr.com/agents"&gt;Overview&lt;/a&gt;&lt;/div&gt;
&lt;div class="next"&gt;&lt;span class="label"&gt;Next&lt;/span&gt; &lt;a href="https://khtdr.com/agents/features"&gt;Features&lt;/a&gt;&lt;/div&gt;
&lt;/nav&gt;
]]&gt;</content:encoded></item><item><title>agents: operating it</title><description> Overview · Getting started · Features · Tools · Running code · CLI, API, embedding · Operating it · How it compares Which port does it use? agent serve listens on port 2137, on 127.0.0.1 only. You can change it. For one start:
agent serve --port 3000 To change it for good, set PORT where the server will see it. Either of these works:
# in ~/.agents/.env, which the server reads on every start PORT=3000 # or in your shell profile (~/.bashrc, ~/.zshrc) export PORT=3000 The CLI finds the server through a separate variable, so if you move the port, move that too:</description><link>https://khtdr.com/agents/running/</link><guid>https://khtdr.com/agents/running/</guid><pubDate>Tue, 15 Sep 2026 00:00:00 -0600</pubDate><author>mazzarelli@gmail.com (Joey Mazzarelli)</author><content:encoded>
&lt;![CDATA[
&lt;link rel="stylesheet" href="https://khtdr.com/css/agents.css" /&gt;
&lt;nav class="agents-nav"&gt;&lt;a href="https://khtdr.com/agents"&gt;Overview&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/start"&gt;Getting started&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/features"&gt;Features&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/tools"&gt;Tools&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/sandbox"&gt;Running code&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/cli-api"&gt;CLI, API, embedding&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;span class="here"&gt;Operating it&lt;/span&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/compare"&gt;How it compares&lt;/a&gt;&lt;/nav&gt;
&lt;h2 id="which-port-does-it-use"&gt;Which port does it use?&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;agent serve&lt;/code&gt; listens on port 2137, on &lt;code&gt;127.0.0.1&lt;/code&gt; only. You can change it. For
one start:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;agent serve --port &lt;span class="m"&gt;3000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To change it for good, set &lt;code&gt;PORT&lt;/code&gt; where the server will see it. Either of these
works:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# in ~/.agents/.env, which the server reads on every start&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# or in your shell profile (~/.bashrc, ~/.zshrc)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The CLI finds the server through a separate variable, so if you move the port,
move that too:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;AGENT_API_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:3000
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="it-says-the-port-is-already-in-use"&gt;It says the port is already in use&lt;/h2&gt;
&lt;p&gt;Most likely another &lt;code&gt;agent serve&lt;/code&gt; is still running, possibly in a terminal you
forgot about. If so, use that one. Otherwise pick a different port as above. The
error message names the port and gives the same options.&lt;/p&gt;
&lt;h2 id="can-i-reach-it-from-another-machine"&gt;Can I reach it from another machine?&lt;/h2&gt;
&lt;p&gt;By default, no. The server binds to &lt;code&gt;127.0.0.1&lt;/code&gt;, and nothing on it asks for a
password, so opening it up is a decision you make on purpose. Set &lt;code&gt;HOST&lt;/code&gt; to listen
wider, and put it behind something that authenticates first:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ~/.agents/.env&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.0.0.0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There is no login, no bearer token, and no notion of a caller anywhere in the
system. Treat reachability as the whole security boundary.&lt;/p&gt;
&lt;h2 id="where-does-everything-live"&gt;Where does everything live?&lt;/h2&gt;
&lt;p&gt;Under &lt;code&gt;~/.agents&lt;/code&gt;. The &lt;code&gt;.env&lt;/code&gt; with your key and settings, the SQLite database, and
the directories tools read from and write to. Back up that directory and you have
backed up the install. Set &lt;code&gt;AGENTS_HOME&lt;/code&gt; to put it somewhere else.&lt;/p&gt;
&lt;h2 id="running-it-against-postgres"&gt;Running it against Postgres&lt;/h2&gt;
&lt;p&gt;SQLite is fine for one machine. Postgres is for when more than one process, or
more than one machine, needs the same agents and history.&lt;/p&gt;
&lt;p&gt;The database has to exist. The tables do not. They get created the first time the
server starts, and a database from an older version picks up any columns it is
missing the same way.&lt;/p&gt;
&lt;p&gt;Set the driver and the connection in &lt;code&gt;~/.agents/.env&lt;/code&gt;, then restart:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ~/.agents/.env&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;STORE_DRIVER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;PGHOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;db.example.internal
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;PGPORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;5432&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;PGUSER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;agents
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;PGPASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;PGDATABASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;agents
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;agent serve
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Agent platform running on http://127.0.0.1:2137 (storage: postgres)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Everything lands in a schema named &lt;code&gt;agents&lt;/code&gt; inside that database, so it can share
a database with other things. Set &lt;code&gt;DB_SCHEMA&lt;/code&gt; to use a different name. The user
needs to be able to create the schema and its tables on the first start. After
that, ordinary read and write is enough.&lt;/p&gt;
&lt;p&gt;Two servers pointed at the same database see the same agents, runs, secrets and
memories. The SQLite file under &lt;code&gt;~/.agents&lt;/code&gt; stays where it was, so switching back
is a matter of changing &lt;code&gt;STORE_DRIVER&lt;/code&gt; again.&lt;/p&gt;
&lt;h2 id="a-run-is-stuck-at-running"&gt;A run is stuck at &amp;ldquo;running&amp;rdquo;&lt;/h2&gt;
&lt;p&gt;It should not stay that way. Runs check in on a heartbeat, and a reaper closes out
the ones that stopped checking in. A crashed run gets recorded as an error rather
than left hanging, and cancel stops the run.&lt;/p&gt;
&lt;p&gt;What the reaper does not do is resume anything. A dead run is closed, not
continued. If that matters for your workload, read the
&lt;a href="https://khtdr.com/agents/compare"&gt;durability section of the Flue comparison&lt;/a&gt;, which says where
this falls short.&lt;/p&gt;
&lt;h2 id="a-paused-run-is-not-the-same-as-a-stuck-run"&gt;A paused run is not the same as a stuck run&lt;/h2&gt;
&lt;p&gt;A run waiting on a person holds no process. It sits in the database with a token,
and it will wait as long as it takes. &lt;code&gt;agent pending&lt;/code&gt; and
&lt;code&gt;GET /api/runs/pending&lt;/code&gt; list everything in that state.&lt;/p&gt;
&lt;p&gt;The one thing the server does resume is a paused run that already got its answer.
Answering is a write to the row, and restarting the run is a second step; if the
server goes down between the two — a deploy landing at the wrong second — the run
would otherwise sit there answered and unfinished forever. So every server sweeps
for that on startup and every thirty seconds after, and picks those runs back up.
It waits a minute first, so it adopts a run nobody is holding rather than racing a
restart already under way.&lt;/p&gt;
&lt;h2 id="upgrading"&gt;Upgrading&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm install -g @upship/agents@latest
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Restart the server. Schema changes apply on start. If you vendored the runtime
with &lt;code&gt;agent bundle&lt;/code&gt;, run that again after updating the CLI.&lt;/p&gt;
&lt;nav class="agents-pager"&gt;
&lt;div class="prev"&gt;&lt;span class="label"&gt;Previous&lt;/span&gt; &lt;a href="https://khtdr.com/agents/cli-api"&gt;CLI, API, embedding&lt;/a&gt;&lt;/div&gt;
&lt;div class="next"&gt;&lt;span class="label"&gt;Next&lt;/span&gt; &lt;a href="https://khtdr.com/agents/compare"&gt;How it compares&lt;/a&gt;&lt;/div&gt;
&lt;/nav&gt;
]]&gt;</content:encoded></item><item><title>agents: running code</title><description> Overview · Getting started · Features · Tools · Running code · CLI, API, embedding · Operating it · How it compares Give an agent the run_script tool and it can write Node, Python, or shell and run it. This is for work you want computed instead of estimated. Arithmetic over a lot of numbers, reshaping a file, checking a pattern against real input. It is not a box for code you have a reason to distrust.</description><link>https://khtdr.com/agents/sandbox/</link><guid>https://khtdr.com/agents/sandbox/</guid><pubDate>Tue, 15 Sep 2026 00:00:00 -0600</pubDate><author>mazzarelli@gmail.com (Joey Mazzarelli)</author><content:encoded>
&lt;![CDATA[
&lt;link rel="stylesheet" href="https://khtdr.com/css/agents.css" /&gt;
&lt;nav class="agents-nav"&gt;&lt;a href="https://khtdr.com/agents"&gt;Overview&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/start"&gt;Getting started&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/features"&gt;Features&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/tools"&gt;Tools&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;span class="here"&gt;Running code&lt;/span&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/cli-api"&gt;CLI, API, embedding&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/running"&gt;Operating it&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/compare"&gt;How it compares&lt;/a&gt;&lt;/nav&gt;
&lt;p class="agents-hero"&gt;
Give an agent the &lt;code&gt;run_script&lt;/code&gt; tool and it can write Node, Python, or
shell and run it. This is for work you want computed instead of estimated.
Arithmetic over a lot of numbers, reshaping a file, checking a pattern against
real input. It is not a box for code you have a reason to distrust.
&lt;/p&gt;
&lt;h2 id="how-safe-is-it"&gt;How safe is it&lt;/h2&gt;
&lt;p&gt;Safer than running it in the server process. Not as safe as a container.&lt;/p&gt;
&lt;p&gt;A script gets its own process with an empty environment. None of
your keys, none of your secrets. It gets a private working directory, a wall
clock time limit, and a cap on how much it can print.&lt;/p&gt;
&lt;p&gt;On Linux it also loses the network and gets a memory limit, if the kernel allows
it. On a Mac it gets neither. A Node script is held to an interpreter heap limit
and that is all.&lt;/p&gt;
&lt;p&gt;What no host gets is a filesystem boundary. A script can still read whatever the
user running the server can read.&lt;/p&gt;
&lt;p&gt;The important part is that it says so. The tool tells the model what it managed
on this machine, and the Tools page prints the same line for you. It does not
claim isolation it does not have.&lt;/p&gt;
&lt;h2 id="what-can-a-script-do-on-my-machine"&gt;What can a script do on my machine&lt;/h2&gt;
&lt;p&gt;Ask the install instead of guessing from the operating system. What it locked
down is probed against the real kernel at startup, so the answer is different on
your laptop and on your server.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;agent brief &lt;span class="p"&gt;|&lt;/span&gt; grep -A2 run_script
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# - **run_script** — Run a short program you wrote in a sandboxed subprocess...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# on this host: node (node), python (python3), sh (bash); subprocess (isolation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# from accidents, not from intent: this host has no namespaces); network NOT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# blocked — this host cannot drop it; memory NOT capped — this host ignores the rlimit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The same line is on the Tools page, and over HTTP:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -s localhost:2137/api/tools/implementations &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt; jq &lt;span class="s1"&gt;&amp;#39;.[] | select(.name==&amp;#34;run_script&amp;#34;) | .hostNote&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It reports three things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Which languages exist here.&lt;/strong&gt; Node, Python 3, and a shell are looked for on
&lt;code&gt;PATH&lt;/code&gt;. The model is only offered the ones that were found.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Whether the network was taken away.&lt;/strong&gt; That needs Linux with
unprivileged user namespaces. Anywhere else it says so.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Whether the memory cap is real.&lt;/strong&gt; A kernel rlimit on Linux, the interpreter&amp;rsquo;s
own heap limit for Node, and nothing at all on macOS, which ignores the rlimit.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Every run records it too. A &lt;code&gt;run_script&lt;/code&gt; result carries a &lt;code&gt;sandbox&lt;/code&gt; field with the
same sentence, so a run from six months ago still says what it was running under.&lt;/p&gt;
&lt;h2 id="turning-it-on"&gt;Turning it on&lt;/h2&gt;
&lt;p&gt;Add &lt;code&gt;run_script&lt;/code&gt; to an agent&amp;rsquo;s tools, like any other tool. There is nothing else
to switch on. Out of the box it gets thirty seconds, a 512 MB memory cap, 64 KB
of output per stream, and no network.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -X POST localhost:2137/api/agents &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;content-type: application/json&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -d &lt;span class="s1"&gt;&amp;#39;{&amp;#34;name&amp;#34;:&amp;#34;analyst&amp;#34;,&amp;#34;description&amp;#34;:&amp;#34;Answers questions about data files&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;systemPrompt&amp;#34;:&amp;#34;Compute answers with a script rather than estimating them.&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;tools&amp;#34;:[&amp;#34;read_file&amp;#34;,&amp;#34;run_script&amp;#34;]}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The script runs in the run&amp;rsquo;s own output directory, which is where &lt;code&gt;read_file&lt;/code&gt; and
&lt;code&gt;write_file&lt;/code&gt; already point. So it can read what an earlier step wrote, and
anything it leaves behind comes back attached to the run. A chart it drew shows up
as an image on the run page.&lt;/p&gt;
&lt;h2 id="changing-the-limits-or-pinning-a-language"&gt;Changing the limits, or pinning a language&lt;/h2&gt;
&lt;p&gt;The limits are deliberately not parameters the model can set. They live on a tool
definition, next to the description:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -X POST localhost:2137/api/tools &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;content-type: application/json&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -d &lt;span class="s1"&gt;&amp;#39;{&amp;#34;name&amp;#34;:&amp;#34;run_python&amp;#34;,&amp;#34;implementation&amp;#34;:&amp;#34;run_script&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;description&amp;#34;:&amp;#34;Run a Python script over the files in this run.&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;parameterOverrides&amp;#34;:{&amp;#34;language&amp;#34;:&amp;#34;python&amp;#34;,&amp;#34;timeoutMs&amp;#34;:120000,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;maxMemoryMb&amp;#34;:2048,&amp;#34;maxOutputBytes&amp;#34;:262144,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;network&amp;#34;:false}}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then give the agent &lt;code&gt;run_python&lt;/code&gt; instead of &lt;code&gt;run_script&lt;/code&gt;. The Tools page does the
same thing without the curl.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;language&lt;/code&gt; pins one of &lt;code&gt;node&lt;/code&gt;, &lt;code&gt;python&lt;/code&gt;, &lt;code&gt;sh&lt;/code&gt;. Pinned, it stops being a question
the model answers. If that interpreter is not installed here, the tool refuses
to build and names what is, rather than failing on the first call.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;network: true&lt;/code&gt; is the only way a script gets the network, and it means nothing
on a host where the network could not be dropped in the first place.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;timeoutMs&lt;/code&gt;, &lt;code&gt;maxMemoryMb&lt;/code&gt; and &lt;code&gt;maxOutputBytes&lt;/code&gt; cap out at ten minutes, 8 GB,
and 4 MB. A typo above the ceiling is rejected when the tool is built, not after
a run has been parked for a day.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Two definitions can point at the same implementation with different limits. A
quick one for arithmetic, a longer one for a large file. Each is versioned.&lt;/p&gt;
&lt;h2 id="turning-it-off"&gt;Turning it off&lt;/h2&gt;
&lt;p&gt;It is off until you ask for it. &lt;code&gt;run_script&lt;/code&gt; only runs for an agent that lists it,
so there is no global switch to throw. Turning it off means taking it out of the
agents that have it. To find them:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -s localhost:2137/api/agents &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt; jq -r &lt;span class="s1"&gt;&amp;#39;.[] | select((.tools // []) | index(&amp;#34;run_script&amp;#34;)) | .name&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then edit those agents and remove it, in the browser or with a &lt;code&gt;PUT&lt;/code&gt;. That is a
save like any other, so the version that had it is still in the history.&lt;/p&gt;
&lt;p&gt;If what you want is a stronger boundary rather than no scripts, isolate the
server. Run &lt;code&gt;agent serve&lt;/code&gt; as its own user, or in a container, or on a machine you
do not mind it reading. A script cannot see your keys or call a model, but it can
read what the server&amp;rsquo;s user can read, and no setting here changes that.&lt;/p&gt;
&lt;h2 id="python-is-not-in-the-list"&gt;Python is not in the list&lt;/h2&gt;
&lt;p&gt;Only what was found on &lt;code&gt;PATH&lt;/code&gt; when the server started gets offered, and that
discovery happens once per process. Installing Python and expecting the running
server to notice will not work. Install it, then restart:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 --version &lt;span class="c1"&gt;# make sure the server&amp;#39;s PATH will find it&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# restart agent serve, then:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;agent brief &lt;span class="p"&gt;|&lt;/span&gt; grep -A2 run_script
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A &lt;code&gt;sh&lt;/code&gt; that is there and a &lt;code&gt;python&lt;/code&gt; that is not is the usual shape of this on a
slim container image.&lt;/p&gt;
&lt;nav class="agents-pager"&gt;
&lt;div class="prev"&gt;&lt;span class="label"&gt;Previous&lt;/span&gt; &lt;a href="https://khtdr.com/agents/tools"&gt;Tools&lt;/a&gt;&lt;/div&gt;
&lt;div class="next"&gt;&lt;span class="label"&gt;Next&lt;/span&gt; &lt;a href="https://khtdr.com/agents/cli-api"&gt;CLI, API, embedding&lt;/a&gt;&lt;/div&gt;
&lt;/nav&gt;
]]&gt;</content:encoded></item><item><title>agents: tools</title><description> Overview · Getting started · Features · Tools · Running code · CLI, API, embedding · Operating it · How it compares Agents are data. Tools are code. That is the one line you have to cross to add a capability, and most of the time you do not have to cross it. Pointing an agent at an HTTP API is a config change. What ships About twenty tools come with it. Files, search, HTTP fetch, OCR, image generation, shared state, durable memory, peer messaging, human gates, self-critique, and a sandboxed script runner. An agent gets the ones you list in its tools array and nothing else.</description><link>https://khtdr.com/agents/tools/</link><guid>https://khtdr.com/agents/tools/</guid><pubDate>Tue, 15 Sep 2026 00:00:00 -0600</pubDate><author>mazzarelli@gmail.com (Joey Mazzarelli)</author><content:encoded>
&lt;![CDATA[
&lt;link rel="stylesheet" href="https://khtdr.com/css/agents.css" /&gt;
&lt;nav class="agents-nav"&gt;&lt;a href="https://khtdr.com/agents"&gt;Overview&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/start"&gt;Getting started&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/features"&gt;Features&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;span class="here"&gt;Tools&lt;/span&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/sandbox"&gt;Running code&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/cli-api"&gt;CLI, API, embedding&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/running"&gt;Operating it&lt;/a&gt; &lt;span aria-hidden="true"&gt;·&lt;/span&gt; &lt;a href="https://khtdr.com/agents/compare"&gt;How it compares&lt;/a&gt;&lt;/nav&gt;
&lt;p class="agents-hero"&gt;
Agents are data. Tools are code. That is the one line you have to cross to add a
capability, and most of the time you do not have to cross it. Pointing an agent at
an HTTP API is a config change.
&lt;/p&gt;
&lt;h2 id="what-ships"&gt;What ships&lt;/h2&gt;
&lt;p&gt;About twenty tools come with it. Files, search, HTTP fetch, OCR, image
generation, shared state, durable memory, peer messaging, human gates,
self-critique, and a sandboxed script runner. An agent gets the ones you list in
its &lt;code&gt;tools&lt;/code&gt; array and nothing else.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;agent tools
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That prints every tool an agent can name, which ones need to be configured first,
and where each one came from. It also lists the files that failed to load and
what went wrong in them. The Tools page in the browser shows the same thing.&lt;/p&gt;
&lt;h2 id="tool-definitions-are-data-too"&gt;Tool definitions are data too&lt;/h2&gt;
&lt;p&gt;The implementation is code. The description, the settings, and the bound
credentials are a row on top of it. That means two definitions can share one
implementation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The same fetch tool pointed at two different accounts.&lt;/li&gt;
&lt;li&gt;Two wordings of a description, A/B tested against the same input.&lt;/li&gt;
&lt;li&gt;A script runner capped at thirty seconds and another one allowed ten minutes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Anything a model should not be choosing belongs there rather than in the
parameters the model fills in. A question&amp;rsquo;s wording, a sandbox&amp;rsquo;s limits, a
namespace, an API key. If the model can set the timeout, the timeout is not a limit.&lt;/p&gt;
&lt;p&gt;Definitions are versioned like everything else, so an old run still records the
description that was live when it ran.&lt;/p&gt;
&lt;h2 id="calling-an-http-api"&gt;Calling an HTTP API&lt;/h2&gt;
&lt;p&gt;Before you write a tool, check whether you need one. If the thing you want is an
HTTP API with a key, the built-in fetch tool plus an encrypted secret gets you
there with no code.&lt;/p&gt;
&lt;p&gt;Pin a definition to one host, put the credential in the headers, and the model
never sees it. A tool that can call one service cannot be talked into calling
another with your key attached.&lt;/p&gt;
&lt;h3 id="it-will-not-touch-your-network-by-default"&gt;It will not touch your network by default&lt;/h3&gt;
&lt;p&gt;The fetch tool refuses anything that is not a public address. Your own machine,
your home or office network, and the ranges cloud providers reserve for
themselves are all turned down. It checks the hostname and the addresses that
name resolves to, and it re-checks every redirect hop, so a public URL cannot
bounce an agent somewhere private.&lt;/p&gt;
&lt;p&gt;That default exists because the platform itself sits on one of those addresses.
Without it, an agent could ask its own server for your stored secrets and read
them back to you in an answer.&lt;/p&gt;
&lt;p&gt;If you do want an agent to reach something on your network, you say so on a tool
definition. You can pin it to a single host while you are there.&lt;/p&gt;
&lt;h2 id="secrets"&gt;Secrets&lt;/h2&gt;
&lt;p&gt;Credentials go in an encrypted store and get referenced by name. The write API is
write-only, so a secret goes in and never comes back out over HTTP. Tool config
refers to it by name, the runtime resolves it at call time, and the model sees
neither the name nor the value.&lt;/p&gt;
&lt;p&gt;Rotating a key is a save. Nothing redeploys.&lt;/p&gt;
&lt;h2 id="writing-your-own-tool"&gt;Writing your own tool&lt;/h2&gt;
&lt;p&gt;Write one file and restart. Everything in the &lt;code&gt;tools&lt;/code&gt; folder next to where you
start the server loads at startup. Putting a file there is the whole installation.
There is no list to add it to.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// tools/whatsapp.ts
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;zod&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineTool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@upship/agents/tools&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;defineTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;parse_whatsapp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Parse a WhatsApp export into messages. Pass `after` to get only what is newer &amp;#34;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;than a previous call. Use this instead of reading the file, since an export is &amp;#34;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;far too big to pass through a tool call.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;parameters&lt;/span&gt;: &lt;span class="kt"&gt;z.object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;path&lt;/span&gt;: &lt;span class="kt"&gt;z.string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Path to the export.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;after&lt;/span&gt;: &lt;span class="kt"&gt;z.string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Cursor from last time. Omit for the whole file.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;after&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;parseExport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;after&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The description is what the model reads to decide whether to call it, so write it
for the model. What it does, when to use it instead of something else, and what it
will not do. Every parameter gets a &lt;code&gt;.describe()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The schema gets used twice. Once to tell the model what the arguments are, and
once to check them when a pipeline step supplies them. A tool you write behaves
like one that shipped.&lt;/p&gt;
&lt;p&gt;A file cannot take a name that already exists: the platform keeps the original
and tells you it did. Changes need a restart, because there is no reliable way to
unload code that is already running. &lt;code&gt;npm run dev&lt;/code&gt;
restarts on its own.&lt;/p&gt;
&lt;h2 id="packages-and-mcp-servers"&gt;Packages and MCP servers&lt;/h2&gt;
&lt;p&gt;A tool package installs like any dependency and registers the same way. An MCP
server connects over stdio or HTTP at boot and its tools show up with a prefix.
Both land in the same registry as the built-ins, which means they get catalog
entries, tool definitions, config binding, and a line in the authoring brief
without any extra work.&lt;/p&gt;
&lt;h2 id="when-not-to-write-a-tool"&gt;When not to write a tool&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;An HTTP API with a key.&lt;/strong&gt; Use the fetch tool and a secret.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A one-off calculation or a parse.&lt;/strong&gt; Let the model &lt;a href="https://khtdr.com/agents/sandbox"&gt;write a script&lt;/a&gt; and run it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both of those are settings. Neither is a file.&lt;/p&gt;
&lt;nav class="agents-pager"&gt;
&lt;div class="prev"&gt;&lt;span class="label"&gt;Previous&lt;/span&gt; &lt;a href="https://khtdr.com/agents/features"&gt;Features&lt;/a&gt;&lt;/div&gt;
&lt;div class="next"&gt;&lt;span class="label"&gt;Next&lt;/span&gt; &lt;a href="https://khtdr.com/agents/sandbox"&gt;Running code&lt;/a&gt;&lt;/div&gt;
&lt;/nav&gt;
]]&gt;</content:encoded></item></channel></rss>