You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
Not one coding session. The work that matters repeats. A session can't tell you that something changed, because it has no yesterday.
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.
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.
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.
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:
Seam only. Take the ~140-line session hook, leave everything else.
Seam + browser gateway. I file the gateway separately, as an optional
extra that ships disabled.
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.
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.
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:
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:
extra that ships disabled.
examples/nightshift/Problem
A scheduled agent that discovers it needs a person has nowhere to put the
question.
That is the whole thing. Cron plus
omnigent rungets you repetition, and astate 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
COLLECTINGand 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
army/— scheduler, FSM, budgets, channel, approvals, lineage/botspage + API clientomnigent/browser/— server-owned Playwright425 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.pyroutes a session'sbrowser_*actions by label,routes_core.py+helpers.pyreserve that label so only the control planecan 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.