Skip to content

Demo agents: visible model failures, one agent per document, folded reasoning - #105

Open
jpcamara wants to merge 19 commits into
feat/markdown-undofrom
feat/agent-polish
Open

Demo agents: visible model failures, one agent per document, folded reasoning#105
jpcamara wants to merge 19 commits into
feat/markdown-undofrom
feat/agent-polish

Conversation

@jpcamara

Copy link
Copy Markdown
Owner

Polish on the demo agents (Lexxy and markdown pages), from a round of hands-on testing.

  • A model failure is reported in the ledger instead of falling back to canned stub text. LlmReviewer raises ModelError with a short reason; StreamJob records it; the agents say "couldn't finish the review: …", remove the heading or blank lines they opened for a draft that never came, put the task back on the list unchecked, and back off from passing changes for thirty seconds. A failure while reacting to a request no longer ends the agent.
  • Task scans respect SCAN_EVERY and the backoff instead of running every idle tick. A failed draft used to be retried two seconds later, opening a new empty heading each time.
  • One agent per document: the controller keeps a registry of running agent threads and answers a second invite with 409. The invite button is disabled while the agent is in the roster; the bar says "the agent left" once it goes.
  • Reasoning per stream: a StreamJob carries a label through its thread, the presence keeps reasoning by label, and the ledger renders one folded block per label (last few lines, click to open). The review's and a draft's reasoning no longer interleave. Detail-less repeats of a recent status do not make new ledger entries.
  • Caret updates while streaming are throttled to four a second and skipped when nothing moved; the last step of a finished stream no longer presents an empty status (the page read it as "left").
  • Unfinished requests (@agent or @agent take alone) are left alone until there is something after them. In the Lexxy agent, a typing burst that ends with Enter acts on the @agent line rather than the empty block after it.
  • "paused" survives the status fade. Shorter introduction and a how-to hint under the buttons on both pages. The consider prompt asks for notes without block numbers.
  • README: failure behaviour, the single-agent rule, the folded ledger. public/markdown.js is now ignored like lexxy.js.

🤖 Generated with Claude Code

https://claude.ai/code/session_017TeaovH2jyHARHJSyQ8Afo

jpcamara and others added 19 commits September 11, 2026 19:41
… reasoning

A model failure used to fall back to the stub's canned text, so a broken key
or a timeout read as the agent's own words. LlmReviewer now raises a
ModelError with a short reason; StreamJob records it, and the agents report
it in the ledger ("couldn't finish the review: the model timed out"), take
out the heading or blank lines opened for a draft that never came, put the
task back on the list unchecked, and leave passing changes alone for thirty
seconds. A failure while reacting to a request no longer ends the agent.

Task scans run on the agent's own schedule (SCAN_EVERY and the backoff)
instead of every idle tick; before, a failed draft was retried two seconds
later, opening a new empty heading each time.

The controller keeps a registry of running agents, so a second invite for
the same document returns 409. The page disables the invite button while an
agent is in the roster, and the bar says "the agent left" once it goes.

Reasoning is filed per stream: a StreamJob carries a label ("the review",
the section title) through its thread, the presence keeps a hash of
reasoning by label, and the ledger renders one folded block per label that
follows the current entry while the stream runs. Folded blocks show the last
few lines; a click opens them. A detail-less repeat of a recent status no
longer makes a new entry.

Caret updates while streaming are sent at most four times a second and only
when something moved; a stream's last step no longer presents an empty
status. "@agent" with nothing after it (or "@agent take" alone) is a request
still being typed and is left alone; in the Lexxy agent, a burst that ends
with Enter acts on the @agent line rather than the empty block after it.
"paused" survives the status fade. Shorter introduction, plus a hint under
the buttons on both pages. The consider prompt asks for notes without block
numbers.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TeaovH2jyHARHJSyQ8Afo
A task that asks for a line got two to four paragraphs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TeaovH2jyHARHJSyQ8Afo
…r in the document

The GLM router took 5 to 18 seconds before its first word because it
reasoned at length first. Reviews and drafts now ask for medium reasoning
effort (first text in about a second, some reasoning still streamed to the
ledger) and answers, edit plans, and the look at a change ask for low
(under a second). AGENT_REASONING and AGENT_QUICK_REASONING set them;
AGENT_FAST_MODEL is optional now and unset by default, since the same
model at low effort is as quick as the separate one was.

Up to two drafts run at once, each a Draft value with its own writer, job,
heading, and title, so a second task no longer waits for the first. A
draft yields only while someone is at the point being written (the block
the words go into or the one after it, or the line either side of it in
markdown) instead of anywhere in the section. Streams at the end of the
document anchor to their own heading (Lexxy) or to the closing newline
(markdown) rather than to "the end", so two streams no longer interleave.

The ledger is stored in the document: the agent appends each status to a
Y.Array named agent-log (capped at 200), and the pages render from it, so
every page shows the same history and a reload keeps it. Reasoning still
comes from presence and attaches to the newest entry. Caret moves and the
heartbeat's fade to "listening" are not logged.

The invite button and the agent's own controls no longer hold follow, and
the page shows the agent's caret once when it arrives. An empty document
no longer crashes the Lexxy agent on its greeting.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TeaovH2jyHARHJSyQ8Afo
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TeaovH2jyHARHJSyQ8Afo
A document invited more than once appended a new "Agent review" heading
each time. A new review now goes at the end of the existing section, and
the heading is only removed on failure when the agent added it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TeaovH2jyHARHJSyQ8Afo
Several streams at once shared the one caret, so text grew in places with
no cursor beside it. The review now finishes before the first task, and
tasks run one after another. AGENT_DRAFTS raises the number of drafts at
once for anyone who wants the parallel mode back.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TeaovH2jyHARHJSyQ8Afo
… up, ledger under the editor

Four things found by using the markdown page as a person would:

The remote caret and highlight vanished after a click and never came back.
The page scrolled the editor to the agent from inside an awareness event;
when that event fired during the editor's own update (a click sets the
local cursor into awareness from inside one), CodeMirror threw "Calls to
EditorView.update are not allowed while an update is in progress" and
disabled the remote-cursor plugin for the rest of the page's life. The
scroll is deferred now.

Requests after the agent's work often did nothing visible. Every @agent
line now gets an immediate "on it" status with the request in it. A
request is answered once per line, not once per wording, so asking the
same thing again works. "this document" is no longer read as a selection;
with nothing selected, "rewrite this" means the paragraph the request was
typed under, including when the request sits right below it with no blank
line. The author of a request is the person whose caret is on the line or
the one after it (they pressed Enter), or the only other person here. A
rewrite of a selection inside a paragraph is held to those words. The
request line's deletion no longer leaves a double blank line.

Follow: typing holds it for eight seconds and a click or scroll for three;
when the hold ends the page catches up with the agent if it moved; the
editor scrolls inside itself and then the page scrolls so the spot is on
screen.

The ledger goes back under the editor. Per-step statuses while answering
or applying an edit are no longer written to it.

Lexxy: the caret at the end of a list is placed on the last item, which
Lexical can draw; the same request changes, untested there for now.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TeaovH2jyHARHJSyQ8Afo
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TeaovH2jyHARHJSyQ8Afo
The page-level scroll referenced a variable scoped to the deferred editor
scroll.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TeaovH2jyHARHJSyQ8Afo
No page header or nav; the people here and the invite button sit on one
row at the top; the editor and preview stop at 42vh so what is under them
stays in view; the invite button is a filled pill.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TeaovH2jyHARHJSyQ8Afo
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TeaovH2jyHARHJSyQ8Afo
Both sized to their own content before, so they ended at different heights.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TeaovH2jyHARHJSyQ8Afo
Cards with soft shadows on an off-white ground, a pulsing dot while the
agent is in, chip buttons, a labelled activity ledger, and the editor
without a hard border.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TeaovH2jyHARHJSyQ8Afo
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TeaovH2jyHARHJSyQ8Afo
On joining, the agent says hello and goes straight to the task list, or to
listening when there is none. "@agent review" (and a button on the
markdown page) writes a review of the document as it is; AGENT_REVIEW=1
restores the review on joining.

Two fixes that surfaced without the review running first: the first caret
update read a timestamp that was never set, which crashed the work loop,
and the crash path stopped the stream in a way that counted the draft as
done. The caret timestamp defaults to zero, and a crashed draft now puts
its task back on the list and says so. "up next" is no longer logged twice
when the announcement and the pickup agree.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TeaovH2jyHARHJSyQ8Afo
…ction

"Tackle any grammar or spelling issues" produced a section listing the
issues. A task whose wording asks for a change to what is there (fix,
correct, tighten, rename, spelling, grammar, and so on) now runs as an
edit plan over the document, applied in place with the task list itself
left alone, and is checked off with "changed N paragraphs in place".
Tasks that start with draft, write, add, create, outline, or prepare
still open a section.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TeaovH2jyHARHJSyQ8Afo
The agent wrote at a steady pace, but the page received it in clumps: the
preview was re-parsed and replaced on nearly every update and the page
scrolled on every caret move, which blocked the browser's main thread for
a second or more at a time, so incoming updates piled up and then landed
together. The preview now renders at most twice a second and only when the
text changed, and the page scrolls only when the caret is off screen.

On the agent side, three related changes: the loop takes a turn every
tenth of a second while writing (a word or so per turn) instead of every
forty milliseconds (two characters, each its own store insert and
broadcast); the pacer never pays out more than half a second of text after
a stall; and an incoming update that leaves the text as it was is not
treated as a change, which was costing a settle wait mid-draft.

A paragraph replacement retypes only the part that differs, so a spelling
fix is a small change in place rather than the paragraph wiped and written
again. A loop stall of over half a second while writing is logged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TeaovH2jyHARHJSyQ8Afo
Whichever key is set is used: FIREWORKS_API_KEY, OPENROUTER_API_KEY, or
ANTHROPIC_API_KEY, with AGENT_PROVIDER to choose when several are and
AGENT_MODEL to override the model. OpenRouter goes through ruby_llm's own
provider and defaults to "openrouter/free", which routes across their free
models so no single one takes all the requests.

Two failures free models produce often now read plainly in the document: a
rate limit says to try again in a minute, and a rejected key says so
instead of repeating the provider's wording.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TeaovH2jyHARHJSyQ8Afo
"openrouter/free" routes to a different provider on each call. Over three
calls it picked three, and one of them answered an edit-plan request by
echoing the JSON template, which the grammar-fix task cannot use. The
default is now nex-agi/nex-n2.5-mini:free, measured through the reviewer at
about a second to the first word of an answer, a correct edit plan in under
one, and three seconds to the first word of a draft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TeaovH2jyHARHJSyQ8Afo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant