Reproduce "Proactive Thought Partners" (arXiv:2609.01588) as a lab page - #634
Open
kcarnold wants to merge 6 commits into
Open
Reproduce "Proactive Thought Partners" (arXiv:2609.01588) as a lab page#634kcarnold wants to merge 6 commits into
kcarnold wants to merge 6 commits into
Conversation
Before writing any code, work out what the paper's probe actually needs and which of it a task-pane add-in can supply. Records eight challenges up front, of which two are decisions rather than obstacles: Execute / "Help Me Write" is omitted because it contradicts the repository's stated covenant, and the paper's session goal is folded into the existing document brief. The paper's prompts are said to be in supplementary materials that the arXiv PDF does not contain, so every prompt in this reproduction is reconstructed from the prose in §4.2-§4.3. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LzPdByDv9KAZyJd6kmTYpR
…apshots The paper's three triggers run on a live keystroke stream inside its own editor. A task pane has no keystrokes on any host, so this derives the same three from diffs of polled getDocContext() snapshots, and derives a coarse activity trace to stand in for the 15-second keystroke log the decision engine reads. The state machine is a pure fold over (state, snapshot, config), so the timing behaviour is tested by feeding it made-up timestamps rather than by running clocks. It carries one parameter the paper does not have: a cooldown between fired triggers, because polling makes triggers coincide in ways keystrokes do not, and because each fired trigger costs a model call. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LzPdByDv9KAZyJd6kmTYpR
…torage Reconstructs the paper's two LLM stages from its prose, since the prompts it points at are in supplementary materials the arXiv PDF does not contain: an engine that judges each partner's contextual heuristic against the moment and selects at most two (preferring silence), and a suggestion of the paper's two parts - an acknowledgement of what the writer is doing, then a question. Both parsers are deliberately tolerant. A model that answers in prose, fences its JSON, invents a partner id, or returns three partners should degrade to something the writer can use, not throw on the writing path. Partners persist as a document setting, which is the only store all three hosts share; the paper's are per-user. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LzPdByDv9KAZyJd6kmTYpR
…ion cards Wires the pieces into a page: configure partners, switch watching on, and tags appear when the decision engine finds a heuristic satisfied. Clicking a tag opens the suggestion and a short follow-up conversation; ignoring it lets the tag fade after fifteen seconds, as in the paper. Two of the paper's three engagement forms are here. The third, where the partner writes into the document, is not, and the page says so on its face. The suggestion is generated on click rather than on activation. The paper can afford eager generation because it runs a cheap model for the decision and a separate one for the suggestion; we have one model and noisier triggers, so an unopened tag should not cost a generation. Adds the page's log events at schema version 6. The interesting one is partners_activated with activated: 0 - the engine choosing not to interrupt, which is the outcome the paper reports as most common. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LzPdByDv9KAZyJd6kmTYpR
…t touch Running the repo-wide format script reformatted five unrelated files - the installed prettier disagrees with what is committed on main. That drift is real but it is not this branch's to fix, and it made the diff harder to read. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LzPdByDv9KAZyJd6kmTYpR
Driving the page end-to-end found two failures that the unit tests could not, because they feed the state machine snapshots rather than watching a writer type. A partner listening only for pauses never fired. Every sentence the writer finished fired a sentence-end that no partner listened for; that produced no activation, but it started the 45-second cooldown, and the pause behind it was suppressed. The feature looked dead while every part of it worked. `observe` now evaluates only the triggers some partner is listening for. The second: a polled observer has no history from before it starts, so its first snapshot becomes the baseline and anything typed in the gap before it is invisible. Sampling the baseline the moment watching starts shrinks that blind window from a poll interval to one host read; it cannot close it. Adds a Playwright spec that configures a partner, types, stops, and opens the resulting tag - the test that found both - and writes both up in the reproduction log, along with what the cooldown means for a study run on this build: it is a scarce resource the noisiest trigger wins by default. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LzPdByDv9KAZyJd6kmTYpR
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
An attempt at reproducing Zhang, Davis, Chen & Hsu, Designing Proactive Thought Partners for Writing (arXiv:2609.01588v1) inside this add-in, plus an honest log of where the reproduction breaks down.
What the paper built
A Next.js app with its own BlockNote editor. Writers configure "partners" (name + emoji, role, event triggers, contextual heuristic); as they write, keystroke-level triggers fire, an LLM decision engine picks at most two whose heuristics match, and each produces an acknowledgement plus a question-style suggestion. Writers can ignore it, open it as inspiration, or have it edit the document.
What is here
A
Partnerslab page (frontend/src/pages/partners/), reachable from the Labs (···) menu. Configure partners, switch watching on, and tags appear when a heuristic is satisfied; clicking one opens the suggestion and a short follow-up conversation; ignoring one lets it fade after fifteen seconds.docs/proactive-partners-reproduction.mdis the map and the challenge log.The interesting problems
There are no keystrokes. The paper's triggers run on a live keystroke stream inside its own editor. A task pane's writer types into Word or Google Docs; Office.js offers only
DocumentSelectionChanged, and Google Docs offers no selection event at all. The triggers are rebuilt from diffs of polledgetDocContext()snapshots, and the 15-second keystroke log becomes a much coarser activity trace. Burst/pause rhythm, backspace runs, and the rest of what the keystroke-analysis literature the paper cites depends on are not observable here."Execute" is deliberately not implemented.
docs/design/interface-concepts.mdopens its covenant with "the writer's sentences are the writer's", andfrontend/CLAUDE.mdstates that nothing in the add-in rewrites the writer's prose. This is the one place where a faithful reproduction and this project's stated values genuinely conflict, so it is flagged rather than quietly resolved — and it means this build cannot speak to the paper's findings about Executing. The machinery exists (EditorAPI.applyEdit), so it is a decision to revisit, not a capability gap.The paper's prompts are not in the PDF. §4.5 says they are in supplementary materials; the arXiv appendix is references only. Every prompt here is reconstructed from prose, so a behavioural difference could be a prompt difference we cannot detect.
Two problems only running it found
Both are consequences of polling rather than listening, so they are evidence about the compromise rather than ordinary bugs.
An unwatched trigger was spending the cooldown. A writer whose only partner listened for Long pause received nothing, ever: every sentence they finished fired a Sentence end that no partner listened for, which produced no activation but started the 45-second cooldown and suppressed the pause behind it. The feature looked dead while every part of it worked. Fixed, with a regression test — but the general point stands and shapes any study run on this build: the cooldown is a scarce resource the noisiest trigger wins by default, so a writer with one sentence-end partner and one pause partner is not running two partners at equal odds. The paper has no cooldown and no such effect.
A polled observer has no history from before it starts. Its first snapshot is indistinguishable from "text just typed", so a writer who switched watching on and immediately typed a sentence and stopped got no pause — the burst had been absorbed into the baseline. Mitigated by sampling at once rather than on the first interval; the blind window shrinks to one host read but cannot close.
Verification
frontend/tests/partners-flows.spec.tsdrives the whole loop in a browser — configure a partner, type, stop, tag appears, open it, get the suggestion and a follow-up. That spec is what found both problems above. 45 unit tests cover the state machine and the two response parsers. Typecheck, lint, and the full 284-test suite pass.Not verified: any real host. Everything ran against the standalone Lexical editor, where reading the document is a function call. On Word each poll is a
Word.runsync and on Google Docs an Apps Script round-trip; whether a 1.5-second poll is tolerable there is an open question.🤖 Generated with Claude Code
https://claude.ai/code/session_01LzPdByDv9KAZyJd6kmTYpR