Skip to content

[Feature] Bot mode: agents that outlive one run  #5367

Description

@Paldom

The ask

Agents are still packaged as sessions: you open one, it works, it ends. Everything durable about it — what it learned, what it was waiting for, what it's allowed to spend — dies with the process. That's the right shape for "write me this function" and the wrong shape for almost everything else a colleague does.

Several products arrived at the same fix independently: Grok bots, OpenBot (the agent loop as a first-class, self-hostable thing), Buzz (an agent that lives where the team already talks), Xirp (agents that produce durable artifacts, not just replies). Different stacks, but same conclusion: the unit is not the session.

What that implies:

  1. Not one coding session. The work that matters repeats. A session can't tell you that something changed, because it has no yesterday.
  2. Loop engineering, not prompt engineering. The hard parts are a run FSM, backoff, budgets, per-vendor lanes, and a parked run that holds no seat. Scheduler problems, not model problems.
  3. Agent-to-agent cooperation. Not agents chatting — durable artifacts one leaves and another reads on its own schedule. It still works when the first is asleep, paused, or retired.
  4. A computer attached. An agent with its own browser, cookies and all, and a human who can take the wheel mid-task. Half the web worth reading is behind a login, a consent wall, or JS that curl never runs.
  5. The interruption model is the product. Approve/deny makes a vending machine. Ask, discuss, and acknowledge-without-authorising is what makes one usable.

Omnigent has the session, the harnesses and the browser already. The missing piece is somewhere durable to hang an identity — which is the seam this issue asks about.

bot-mode.mp4

I have a working implementation of long-running "bot" agents on a fork. The control plane stays in my fork permanently.
I'm asking one question about seams, and any of three answers is useful:

  1. Seam only. Take the ~140-line session hook, leave everything else.
  2. Seam + browser gateway. I file the gateway separately, as an optional
    extra that ships disabled.

Problem

A scheduled agent that discovers it needs a person has nowhere to put the
question.

That is the whole thing. Cron plus omnigent run gets you repetition, and a
state file on disk gets you a baseline. I'm not claiming otherwise. What it
does not get you is a run that stops mid-work for a human decision and
resumes from that decision three days later with its work intact
. Today the
process either exits (losing the work) or blocks (holding a vendor seat for
three days). Neither is a thing you can run ten of.

Everything else I built follows from needing somewhere for that question to
wait: an identity that outlives the run, a budget that survives a crash, a
mailbox, and a place a person can look to see what is pending.

The work this is for is the kind where the reading is the hard part and there
is no API to poll — a supplier's plan page behind a login where what's included
in your tier quietly changed, a terms revision that may or may not touch you.
If a vendor publishes JSON, poll the JSON; that is not what this is for.

What "bot mode" is

A bot is data, not a process: rows for its charter, schedule, budget and
every question it asked. Each iteration runs in an ordinary Omnigent session
that is then discarded. Kill the loop mid-iteration and nothing is lost: the
run is still in COLLECTING and the next tick collects it.

The parts that turned out to be load-bearing are all scheduler problems rather
than model problems: a run FSM with legal transitions, status derived from rows
rather than stored, backoff on "nothing to report", per-vendor lanes, budget
charged in the same transaction that creates the run, and the one that matters
most: a parked run holds no concurrency slot, so three unanswered questions
cannot stall the fleet behind them.

Two things beyond the scheduler are worth naming because they are what make it
usable rather than merely durable:

Bots read each other's work. Not agents chatting, but agents leaving
durable artifacts another reads on its own schedule. It still works when the
first bot is asleep, paused, or was retired last month.

A browser each, with control you can take back. Each bot drives its own
Chromium profile; a person watching sees that same page, and can take the
wheel, at which point the bot's actions are refused rather than queued,
including reads, because a snapshot taken while somebody is typing a password
transcribes it. A bot that meets a login asks a person to sign in rather than
handling a credential, and the executor refuses to type into password and
one-time-code fields regardless of what the page argues.

Cost, honestly

Piece Size New deps Where it lives
army/ — scheduler, FSM, budgets, channel, approvals, lineage 14,200 lines none my fork, permanently
/bots page + API client ~2,000 lines none my fork
omnigent/browser/ — server-owned Playwright 1,150 lines Playwright + ~290 MB Chromium ask (2), optional, off unless installed
Session seam — routing + a reserved label +140 / −1 across 3 files none ask (1)

425 tests on the fork; the seam itself is covered by 16.

The 140 lines are the honest number for what upstream would inherit under ask
(1). routes_browser.py routes a session's browser_* actions by label,
routes_core.py + helpers.py reserve that label so only the control plane
can set it. The server imports nothing from army/.

Ask (2) is genuinely invasive and I would rather file it separately. Upstream
would inherit a heavyweight dependency, headless-browser operations, a standing
CVE stream, and a security review of "our agent is logged into things". If that
is a non-starter, say so plainly. Ask (1) alone is enough to keep this working
out of tree, and I will carry the browser myself.

Alternatives

Scheduled tasks. Omnigent has these and they are the right primitive for
"do this at this time". A bot is the other axis: durable identity across many
such runs. The two compose — a bot could be scheduled by one.

Sub-agents. Solve fan-out inside one session. This is fan-out across time,
where the coordinating context is gone before the second agent runs.

Keep it a fork. Entirely reasonable, and it is option 3. The only thing I'd
lose is that every iteration is amnesiac without somewhere durable to hang an
identity, which is exactly what the 140 lines buy.

Prior art

CopilotKit's OpenBot exposes the loop but leaves state to the caller, which is
the part that turns out to be the hard 80%. The interesting question is not
whether to copy it but whether a general-purpose platform should own that state
or hand out a hook for someone else to.

Metadata

Metadata

Assignees

Labels

FeatureNew feature or requestP2-mediumPriority: bug with workaround, important feature requestcomp:serverComponent: server, API, session managementhelp wantedExtra attention is neededtriagedIssue has been triaged by the bot

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions