Agents · Objectives · Evolution
A desktop workspace for work you hand to a crew of agents and then walk away from.
English · 中文
Quickstart · Agents · Objectives · Evolution · How it works · Where it stands
A workspace on first run — the crew on the left, what master offers to do in the middle, what drives it on the right.
Each workspace has a resident agent called master. You tell it what you want. It puts a crew of helpers on the work, keeps going while you are elsewhere, leaves files behind, and folds what it learned back into itself. Any agent on the machine can be opened, read and talked to.
It is built for work you would rather check on than watch: a batch of variations to compare, a document that gets rewritten five times, something to keep an eye on while you are away. It is not an IDE and not a pair-programmer — no editor, no git integration, no test runner. What comes back is files in a directory and a record of how they got there.
It runs locally: an Electron client over a prime-agent daemon with a persistent Python kernel. Sessions, files and harness state — the agent's own mutable prompt and memory, as opposed to its fixed base prompt — stay on your machine. AOE itself sends nothing but model calls and one web font; what an agent's own tools fetch is up to the work you give it.
One rule runs through the whole interface:
What it shows is read from the runtime, never from the model's account of itself. If a panel says an objective is running, the daemon said so.
An agent that reports success is making a claim. A version you can diff, a token count against a budget, a lesson with its evidence attached, and a helper the daemon still answers for are facts.
The name is the three things the window is made of, and the three sections below: the agents you put on the work, the objectives that keep them going without you, and the evolution they keep afterwards. It is early — no binaries, build from source, and Where it stands spells out what is missing.
Look around first, in about two minutes. No runtime and no daemon: the real shell in model-only chat, without the crew, the drivers or the lessons.
git clone https://github.com/shitianfang/aoe && cd aoe
npm install
cp .env.example .env # then paste a real NVIDIA NIM key — https://build.nvidia.com
npm run dev # renderer on http://localhost:3000
npm run app # the Electron shell, in a second terminalThe real thing needs Node ≥ 22.8 and uv on PATH — the daemon and its Python kernel require them. Developed on Linux and macOS; see Where it stands for Windows.
npm run core:install # the agent runtime, vendored in core/
npm run core:build # build it once — the bridge loads its distGive the runtime a model. prime-agent keeps its own provider config in ~/.prime/agent/ and
the app reads whatever is there: Anthropic, OpenAI, Google, OpenRouter, Groq, DeepSeek, Prime
Inference and a couple of dozen more are built in, plus any OpenAI-compatible endpoint added
by hand in ~/.prime/agent/models.json. The NVIDIA NIM key above is the model-only fallback,
not a runtime provider.
./core/prime-agent.sh # then /login, and pick a subscription or API-key providerThen three processes, one per terminal. Leave all three running:
npm run bridge # the Node process between app and daemon, on 127.0.0.1:3117
npm run dev # renderer on http://localhost:3000
npm run app # the Electron shellnpm run bridge starts the daemon too. The app opens in Chinese; English is one click away
under the avatar. The first run offers a few things to ask for rather than an empty box. Send
one — "Three helpers, one each: a name, a palette, a tagline for a new app — then pick the
best set" — and watch the left column fill up.
Warning
The runtime executes model-generated Python with your user permissions, and unattended mode
exists precisely so an agent does not stop to ask. Worker and kernel processes give
lifecycle isolation, not a security sandbox. Point a workspace at a directory you can
afford to lose, and keep untrusted instructions, skills and extensions out of it.
The bridge binds 127.0.0.1, but it answers any origin, so any page open in your browser
can drive your agents. npm run dev also serves the renderer on 0.0.0.0 with the host
check off and proxies /bridge straight through — anything that can reach port 3000 has the
same control. Trusted network only.
To stop a run: while an agent is working and the composer is empty, the send button is
stop, which aborts the turn — type into it and it stays send, which steers instead.
Closing the app does not stop anything: workers are resident. Clear the objective or switch
unattended off so it does not start again, and pkill -f prime-agent ends everything except
the Python kernel.
If something is off:
| Symptom | Cause | Fix |
|---|---|---|
| Settings says model only; no crew, no drivers | the bridge is not running, or the daemon could not start | read the terminal running npm run bridge — it says which |
| The daemon dies at startup, or no helper ever spawns | uv missing, or Node older than 22.8 |
install uv; node -v in the bridge's own terminal |
EADDRINUSE on 3117 |
an older bridge is still alive | pkill -f "electron/bridge[.]mjs", then start it again |
| The composer has no model picker | no provider configured yet | ./core/prime-agent.sh, then /login |
A crew, not a chat window.
- master is resident, one per workspace. It spawns helpers for parallel or background work, and it is not the only agent on the daemon: the Agents column lists master, its helpers, every other root — a top-level session of its own, not spawned by anyone — and those roots' crews.
- Every row says what that agent is doing: the line the agent writes about its current step, or else the task it was given, next to a status the daemon reports and a finish time. An agent with neither gets a bit of stable flavour for the state it is in, so no row is blank. Team rows count how many are running, how many need you, how many failed.
- Any live agent opens as a tab: the full transcript, streaming, with tool rows that name the file touched or the first line of code run. Attaching folds older turns into one N earlier turns · show row instead of dumping the history at you.
- Talk to any of them — the composer's
topicker, or the composer inside that agent's own pane. A message to a helper comes back delivered or queued; an idle root is woken; a running root is steered. - A helper's panel says what a helper is: its task, status, finish time, the model it was spawned with (fixed at spawn, so it is shown rather than switched), the tokens it spent against master's budget, and whether it is still reachable or ran inline and is gone. Helpers have no objectives, unattended mode or check-ins of their own, and the panel leaves those rows out rather than filling them in.
- Stop or remove a helper; create or delete roots straight from the column.
Work that keeps itself going.
The composer has one switch: long-running. Turning it on starts nothing. It prefixes your next message with an explicit ask — set up exactly one driver, the runtime mechanism that decides when the agent acts next, and say in one line which you picked and why:
| Driver | What the agent calls | For |
|---|---|---|
| Objective | goal.create(objective, token_budget=…) |
work pursued across many turns until it is achieved — the ask names 400k tokens when you don't |
| Check-in | rlm_heartbeat.create(instruction, interval=…) |
work revisited on a schedule rather than run continuously — the runtime calls these heartbeats |
| Unattended | autonomous.enable(turns=…, tokens=…, time=…, continuations=…) |
one long task that must not stall at the first ambiguity |
The client never picks for you and never turns a driver on behind your back. The agent decides, states its choice, and whatever it started shows up in the Inspector on its own. Your timeline keeps your own words plus a note that the ask was made — that prefix is never passed off as something you typed. The switch is per agent and per session, and does not come back silently after a restart.
The Inspector is the record of what is driving the selected agent, and where you drive it by hand:
- Objective — the objective, its status, tokens used against its budget; pause, resume, clear, or set one inline. The header reads driven by you or driven by objective.
- Unattended — continuations, turns, tokens and elapsed time against their limits, and why the last continuation was injected. In practice that reason is always a turn that ended without evidence: the failed-check case needs gate commands, which only a daemon launched with them has. Limits are editable before you switch it on, prefilled with the runtime's defaults: 12 turns, 80k tokens, 30 minutes, 3 continuations.
- Re-entry — the check-ins above and any scheduled jobs, with their next run time; pause, resume, cancel. A new check-in is an interval and a plain instruction. There is no cron syntax to learn — and no cron power either.
Objective, unattended and check-ins bind to whichever agent is selected; while a root's state is still arriving the panel says so, rather than claiming there is no objective. Two things are deliberately not per-agent, and are labelled that way: scheduled jobs are master's, and the auto-learn switch below is the machine's.
What an agent learns, it keeps.
A lesson is what the runtime's refiner — its own review pass over the trajectory, not a separate agent — writes when it finds something worth keeping: small, evidence-backed edits to that agent's harness state — supplemental prompt, memories, skill and subagent descriptions. The immutable base prompt is never touched. This is prime-agent's continual harness, surfaced as a part of the product you can read, undo and spread.
- Learn now runs a review of the selected agent's session and harness, with an optional line about what to focus on. It takes minutes; the finished lesson turns up on its own as a row in the Self-evolution column, and — for master — as a card in the timeline as it lands.
- Auto-learn is one switch for the whole machine: every agent, every workspace. It writes the runtime's own global setting, and the bridge reloads every live root worker so the change takes hold now. The Inspector states the rhythm: when the last automatic review ran, and the earliest the next one can.
- The Self-evolution column (the rail's lightning icon) merges every kept lesson into two groups — for one agent, where master's and every root's are interleaved newest first with the owner on each row, and for every workspace. A dot on the rail marks anything that landed since you last looked.
- Opening a lesson shows the record: its summary, the harness's own evidence for keeping it, what it expects to change (stated by the refiner, never verified by the system), the edits it applied, its scope, and its origin — automatic, asked for, or the agent's own idea. A lesson caught live in the timeline shows one thing more: each edit with its before and after.
Two operations. Roll back undoes a lesson in one step; the rollback is itself recorded as a lesson, and its own detail pane will not offer to roll it back again. Apply everywhere runs a fresh review in the global scope, seeded with that lesson's summary — the result is a new lesson that applies to every session on the machine, and it may come out differently from the local one.
The runtime's only tool is a Python REPL, so writes happen inside the kernel and no tool
argument can be trusted as a record of what changed. The bridge scans the workspace at each
turn end and diffs it against the previous manifest. The Files column is that diff, plus
whatever an agent publishes explicitly: what changed, who changed it, when. The scan stops at
four levels deep and skips anything dot-prefixed, node_modules and .git, so a file written
to .out/report.html will not appear.
Preview opens .html (in a sandboxed iframe), .md, .png and .pdf. Every turn that
changes a file's content snapshots a version, and the view puts the last two side by side
with the tool calls and lessons that happened between them listed underneath. Agents can also
declare a work
product explicitly, which snapshots immediately under the agent's own label. Snapshots dedupe
by path and content hash, so a file both declared and seen by the scan in one turn is still
one version. They are kept as real files under ~/.prime/desktop/.previews/, and nothing
prunes them — a page you iterate on all day leaves a copy per turn.
The client is not neutral about how work gets done here. Every session AOE creates gets an appended system prompt — never a replacement for the runtime's own — built around three things, because a deliverable that skipped any one of them is the same failure every time: a beautiful thing nobody asked for, a broken thing nobody looked at, or five rounds that changed nothing.
Align. For anything with a shape, the first turn writes four genuinely different takes as
four files, publishes each, says in one line what each trades away, recommends one and says
why, and stops for the user to pick. That turn plans; it does not build. The moment the user
picks, the agent writes .review/brief.md — the take they chose, what it gives up, and three
checkable criteria — and nothing gets built until that file exists. It is the contract every
later round, every judge and the final report is measured against.
Verify. Nothing is published on the strength of its own author reading its own source.
GET /bridge/shot?path=<file>&out=<png> renders the candidate offscreen exactly as Preview
will and hands back a PNG plus what the page got wrong loading it — broken images, sideways
scroll at the target width, console errors, a body with no text on it. The agent looks at the
picture (attach_image), answers the brief's criteria one at a time in .review/check.md,
and a candidate that fails one never reaches the judges. Where no renderer is available the
shot says so and the round is marked as unverified rather than quietly skipped.
Iterate. A round reads the previous version off disk, names three to five properties with
before→after values (one of them large enough to see at thumbnail size), and produces two or
three mutants pulling in directions that cannot both be right. A gate rejects any candidate
whose diff does not carry its own targets. Subagent judges then pick between the pictures,
blind and shuffled, in three lenses — does it do the job, is it well made, what fails first —
answering WINNER, GLANCE (visible in two seconds?), SAW (image or source) and, for the
adversarial lens, BREAKS: a criterion the winner still fails vetoes the round however the
votes fell. Only a winner is written back and published. Two losses in a row send the work
back to the align turn's runner-up instead of a third variation of a dead track; when every
criterion passes and a round fails to beat the incumbent, that is done, and it gets said.
Whatever the rounds taught goes to refine.run(…) so it outlives the task — it shows up in
the Self-evolution column, where you can read it or roll it back.
The longer form is a skill shipped in this repo, skills/aoe-way,
which the bridge hands the runtime alongside its own — with the runnable version of each of
those: the gate, the shot, the blind panel, the tally, the stop conditions. Edit it, and the
agents in your workspaces work differently. The appended prompt is fixed when a session is
created, so an edit reaches new sessions immediately and a running master on its next cold
start; the skill file is read per session the same way.
Each directory under ~/.prime/desktop/ is a workspace with its own resident master;
general is the pinned default. The rail's logo switches between them and creates new ones,
saying which masters are running. It reopens where you left off.
- Up to four center panes on a 2×2 grid, each with its own tab group. Drag a tab — or an agent straight out of the column — past a pane's edge to split there, or into its middle to add it as a tab. Each conversation pane gets its own composer, bound to the agent it shows, and the layout is stored per workspace.
- Chinese and English, light and dark, switched under the avatar. Agent replies render as Markdown, including half-written ones mid-stream.
- Skills and Extensions are read-only catalogs of what the runtime has available: skills, providers, MCP servers, extensions.
┌──────────────────────────────────────────────────────────────────┐
│ renderer — React in Electron, no state framework │
│ agents · timeline · inspector · self-evolution · files · preview│
└───────────────────────────────┬──────────────────────────────────┘
│ HTTP + SSE on 127.0.0.1:3117
┌───────────────────────────────▼──────────────────────────────────┐
│ bridge — electron/bridge.mjs (Node) │
│ workspaces · attach and steer · turn-end file diff · snapshots │
└───────────────────────────────┬──────────────────────────────────┘
│ daemon protocol v7, JSONL over a
│ unix socket (named pipe on Windows)
┌───────────────────────────────▼──────────────────────────────────┐
│ prime-agent daemon — core/ │
│ supervisor ─▶ one worker per root session ─▶ Python kernel │
└──────────────────────────────────────────────────────────────────┘
The bridge exists for two reasons. A renderer cannot open a unix socket, and prime-agent's launch path treats a running daemon whose protocol version, schema id or app version differs from its own as stale and replaces it — so the SDK that speaks to a daemon has to be the same build. One Node process owns that connection, holds the attachments, and fans events out as SSE. Detaching the client does not stop a worker: close the app and the crew keeps working.
With the runtime connected, the composer's picker switches the model of the session you are
talking to, drawn from the providers configured in ~/.prime/agent/. Master answers for its
own; each root for its own. Helpers get no picker, because a helper's model is decided when
master spawns it.
Without the runtime, the app degrades to model-only chat rather than breaking:
- NVIDIA NIM —
NIM_API_KEYfrom.env, proxied server-side; the renderer never sees a key. Keys come from build.nvidia.com;NIM_MODELdefaults todeepseek-ai/deepseek-v4-pro-0813. - Vercel AI Gateway —
AI_GATEWAY_API_KEYfrom.env, proxied the same way. One key reaches four vendors, and the picker offers four models:anthropic/claude-opus-5,moonshotai/kimi-k3,deepseek/deepseek-v4-flashandstepfun/step-3.7-flash. Keys come from the AI tab of a Vercel project.
NVIDIA tells you nothing about your own rate limit: a NIM response carries no X-RateLimit-*
header on success or on 429, and there is no usage endpoint to ask. The free tier is about
40 requests per minute per key, shared across every model — and you find out you crossed
it by getting a 429.
So the bridge counts. Every NIM request passes through it (ALL /nim/* proxies to
integrate.api.nvidia.com) and GET /bridge/nim reports the trailing minute; the composer
shows it beside the model picker as 12/40, taking colour only when the minute is nearly
spent or a 429 just came back. Concurrency runs out first in practice — around five requests
in flight starts drawing 429s well before the minute is used up — so the payload carries
inflight too, and the tooltip says so.
Two consequences worth knowing:
- For the count to be the whole truth, the runtime must reach NIM through the bridge as
well. Set the provider's
baseUrlin~/.prime/agent/models.jsontohttp://127.0.0.1:3117/nim/v1rather than to NVIDIA directly. A session resolves its model when it is created or switched, so re-pick the model once after changing this. - That makes the bridge a hard dependency for model traffic: with the bridge down, the
runtime cannot reach NIM at all — including
./core/prime-agent.shrun on its own. PointbaseUrlback athttps://integrate.api.nvidia.com/v1if you want the runtime independent, and the readout then only counts what the app itself sends.
The gateway speaks the OpenAI chat API and routes a creator/model id on to the vendor, so it
needs no client of its own — the bridge proxies ALL /gw/* to ai-gateway.vercel.sh and
attaches the key there. Whatever Authorization a caller sends is dropped rather than
forwarded: no page, and no runtime, ever has to hold the key.
Where NVIDIA says nothing about your budget, Vercel answers for the account. GET /bridge/gateway reads its /v1/credits and the composer shows the balance beside the picker
as $4.98, amber under a dollar. That number covers the runtime's spend as well as the app's,
whichever road the request took.
To offer the same four models to the runtime, put them in ~/.prime/agent/models.json
under a vercel-ai-gateway provider with baseUrl set to http://127.0.0.1:3117/gw/v1, and
give the provider any placeholder credential in ~/.prime/agent/auth.json — it only has to
count as configured, since the bridge replaces the key on the way out. prime-agent ships a
generated catalog of 200-odd gateway models, so the bridge treats a provider written into
models.json as a declaration rather than an addition: the picker offers exactly the models
listed there, and providers models.json says nothing about are passed through whole.
A free-tier key reaches only part of the catalog. Opus 5, Kimi K3 and DeepSeek V4 Flash answer
403 Free tier users do not have access to this model until the account has paid credit;
Step 3.7 Flash runs. The gateway's own sentence is what the composer shows.
| Variable | What it does | Default |
|---|---|---|
PRIME_AGENT_DIR |
which runtime build the bridge loads | this repo's core/ |
PRIME_BRIDGE_PORT |
bridge port | 3117 |
PRIME_WORKSPACE_ROOT |
where workspaces live | ~/.prime/desktop |
PRIME_WORKSPACE |
workspace to open | last opened, else general |
PRIME_AGENT_DAEMON_SOCKET |
daemon socket path | the SDK's platform default |
NIM_API_KEY |
key for the fallback chat provider | — |
NIM_MODEL |
model for the fallback chat provider | deepseek-ai/deepseek-v4-pro-0813 |
NIM_RPM |
requests/minute the NIM readout counts against | 40 (free tier) |
AI_GATEWAY_API_KEY |
key for the Vercel AI Gateway provider | — |
AOE_DEV_URL |
dev server the Electron shell loads | http://localhost:3000 |
AOE_DEBUG_TURNS |
log every roster turn end | off |
.env holds the NIM and gateway keys and is gitignored. The bridge reads it itself, so
npm run bridge has them however it was started.
- Everything above is wired to a live daemon, not mocked. Helper crews and the
long-running drivers were walked through end to end and written up — see
docs/e2e-walkthrough-1.md and the two findings docs, all three
in Chinese, and all three written before the runtime was vendored into
core/, when the daemon was still at schema 25. Lessons, rollback and previews run against real runtime calls but have no published walkthrough yet. - There are no published binaries. Build from source.
- Windows zips build, but have not been validated on real Windows hardware. Linux and macOS are what development runs on.
- The fork's changes are offered upstream — not yet as a pull request. The client degrades without them; the table below says exactly how.
- What is not modelled: cost in money (tokens only), any sandbox around the kernel, and any measurement of whether a lesson made an agent better — the refiner's expectation is shown, never verified.
- No accounts, no analytics. One user, one machine.
npm run dist:win # zip in release/ — dist:mac (zip) and dist:linux (AppImage, tar.gz) tooPackaged builds do not bundle core/, so a target machine needs:
NIM_API_KEY/AI_GATEWAY_API_KEYin the environment, or%APPDATA%/AOE/config.jsonholding{ "nimApiKey": "nvapi-…", "gatewayApiKey": "vck_…" }- for the real runtime: Node ≥ 22.8 and uv on PATH, and
PRIME_AGENT_DIRpointing at a built prime-agent checkout — otherwise the app runs in model-only mode
The client is not welded to the vendored runtime. It speaks daemon protocol v7 over a local
socket and takes three things out of the runtime directory — packages/coding-agent/dist/index.js
as its SDK, packages/coding-agent/dist/cli.js to spawn the daemon when none is up, and
packages/coding-agent/skills/ as one source for the Skills catalog — so PRIME_AGENT_DIR
can point at any built prime-agent checkout.
core/ carries changes that are not upstream yet, which raise the daemon schema from 25 to
27. Against an upstream checkout every schema-27 path degrades rather than fails:
| Fork surface | What it drives here | Against upstream (schema 25) |
|---|---|---|
preview_events / preview_published |
snapshot the moment work is published, its label, its timeline chip | the turn-end filesystem scan — which always runs anyway — becomes the only source, and nothing is labelled published |
connection-state autonomous |
the unattended panel's live readout | comes back null; the panel still offers its limits and the turn-on control, but never reports progress |
connection-state autoRefine |
auto-learn switch and next-review time | the switch survives: the bridge reads settings.json instead. Only the last review / next no earlier than line goes |
RefinementResult.source |
lesson origin label | no label shown |
helper completedAt |
real helper finish time | no finish time; the status word stands alone |
npm run core:pull pulls the fork forward. Once these land upstream, core/ can track
upstream directly. NOTICE records the vendored commit and every change the fork
carries.
- docs/e2e-walkthrough-1.md — one full session walked end to end
- docs/daemon-integration.md — how the daemon is driven: topology, envelopes, the read/write surface of every mechanism, and the risks
- docs/helper-runtime-findings.md — measured behaviour of RLM (recursive language model) helpers: event shapes, multi-attach, what the kernel needs
- prime-agent-client-handoff — the interaction handoff behind the design
Issues and pull requests are welcome. Runtime changes belong in
prime-agent itself — what core/ carries
today is a fork waiting on upstream, not a place to build on.
AOE is MIT — see LICENSE.
The agent runtime under core/ is a vendored fork of
prime-agent, MIT © Mario Zechner and
Prime Intellect. Its license is retained at core/LICENSE, and NOTICE
records which commit was vendored and which changes the fork carries.