Skip to content

Commit a09d101

Browse files
committed
v55: reconcile dashboard DOM in place and bound conversational inference
1 parent 073c9e0 commit a09d101

7 files changed

Lines changed: 136 additions & 20 deletions

File tree

‎V100_AUDIT.md‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,3 +625,25 @@ the existing game guard and 50 percent target. Windows runtime validation remain
625625
required. Dashboard keyed details and their existing text nodes are retained and
626626
updated in place, including nested text scroll and open/closed state. This replaces
627627
reconstruction of reading nodes, not just restoration of their open flags.
628+
629+
## v55: transactional dashboard refresh and one-turn conversational questions
630+
631+
All periodic dashboard render paths prepare a detached view and reconcile keyed
632+
existing DOM nodes. Reading containers, nested text nodes, open state, scroll and
633+
handlers remain attached in the published view. The previous page-anchor scroll
634+
adjustment is removed. Older full telemetry cannot overwrite fresher inference
635+
output. Node tests cover repeated updates retaining the same reader and text
636+
objects and all scroll offsets; a browser executable is unavailable here.
637+
638+
Read-only conversational questions use one bounded generation instead of three
639+
research tool-routing rounds. Explicit work requests retain executable tools;
640+
unsolicited actions from a conversational reply are rejected. This reduces avoidable
641+
work without claiming that an unavailable or saturated backend always answers.
642+
The supplied GPU image shows loading-context and 4 MiB, which is not evidence of
643+
a persistent CUDA failure. Hardware logs and completion after model load are
644+
required to establish the actual inference/training state.
645+
646+
Validation: 1018 passed, 65 skipped, one resource-dependent worker integration
647+
deselected, one existing PEFT warning. Ruff and upgrade shell checks pass. The
648+
transaction test executes four refreshes and checks actual object identity for
649+
reader/text nodes, expanded state and page/card/text scroll offsets.

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "rlms"
33
authors = [
44
{name = "Alex Zhang", email = "altzhang@mit.edu"},
55
]
6-
version = "0.1.3+v100.54"
6+
version = "0.1.3+v100.55"
77
description = "Recursive Language Models."
88
readme = "README.md"
99
license = "MIT"

‎rlm/v100/dashboard_layout.py‎

Lines changed: 27 additions & 16 deletions
Large diffs are not rendered by default.

‎rlm/v100/mission_chat.py‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,17 @@ def schema(allow_long_goal: bool = False) -> dict:
232232
}
233233

234234

235+
def conversation_question(message: str) -> bool:
236+
"""Read-only questions take one generation; explicit work retains tools."""
237+
value = message.casefold().strip()
238+
return bool(re.match(r"(?:a\s+)?(?:co|czy|jak|dlaczego|kiedy|what|why)\b", value)) and not bool(
239+
re.search(
240+
r"\b(?:zrób|zrob|napraw|popraw|dodaj|edyt|zmień|zmien|uruchom|sprawdź|sprawdz|zbadaj|zaimplement|chcę|chce|możesz|mozesz)\w*",
241+
value,
242+
)
243+
)
244+
245+
235246
def respond(root: Path, directory: Path, request: dict, accepted_cpu=None) -> dict:
236247
from rlm.v100.agent import native_turn
237248
from rlm.v100.competition import helper_client
@@ -420,6 +431,9 @@ def respond(root: Path, directory: Path, request: dict, accepted_cpu=None) -> di
420431
from rlm.v100 import chat_resources
421432

422433
resource_request = chat_resources.instruction(message)
434+
question_only = conversation_question(message) and not resource_request
435+
if question_only:
436+
client.sampling_args["max_tokens"] = min(768, client.sampling_args["max_tokens"])
423437
with connect(root) as db:
424438
history = db.execute(
425439
"SELECT message,response FROM requests WHERE state='completed' ORDER BY rowid DESC LIMIT 3"
@@ -463,7 +477,7 @@ def respond(root: Path, directory: Path, request: dict, accepted_cpu=None) -> di
463477
),
464478
}
465479
]
466-
for old in reversed([] if resource_request else history):
480+
for old in reversed([] if resource_request else (history[:1] if question_only else history)):
467481
messages += [
468482
{"role": "user", "content": old["message"][:1500]},
469483
{"role": "assistant", "content": json.loads(old["response"])["answer"][:1500]},
@@ -501,7 +515,7 @@ def respond(root: Path, directory: Path, request: dict, accepted_cpu=None) -> di
501515
from rlm.v100.dashboard_editor import status as dashboard_status
502516

503517
dashboard_before = dashboard_status(root)
504-
if profile.get("resources", {}).get("interactive_lab"):
518+
if profile.get("resources", {}).get("interactive_lab") and not question_only:
505519
from rlm.v100.research_tools import research_turn
506520

507521
client.research_owner = "A"
@@ -546,6 +560,8 @@ def respond(root: Path, directory: Path, request: dict, accepted_cpu=None) -> di
546560
response_format={"type": "json_object", "schema": schema(allow_long_goal)},
547561
)["content"]
548562
)
563+
if question_only and result.get("actions"):
564+
raise ValueError("Read-only chat question cannot execute unsolicited actions")
549565
if set(result) != {"answer", "actions"} or not isinstance(result["answer"], str):
550566
raise ValueError("Invalid chat response")
551567
if resource_request:

‎tests/test_v100_chat_backend.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,18 @@ def test_capability_question_needs_no_inference_or_profile(tmp_path, monkeypatch
180180
assert result["responder"]["model"] == "controller-capabilities"
181181
assert result["actions"] == []
182182
assert "nie gwarantuję zysku" in result["answer"]
183+
184+
185+
@pytest.mark.parametrize(
186+
"message", ["a co z zarabianiem", "co robisz z wynikami", "jak działa ML?"]
187+
)
188+
def test_readonly_question_uses_conversation_route(message):
189+
assert mission_chat.conversation_question(message)
190+
191+
192+
@pytest.mark.parametrize(
193+
"message",
194+
["napraw dashboard", "czy możesz uruchomić test", "co zrobić? zaimplementuj test", "zmien cel"],
195+
)
196+
def test_explicit_work_keeps_executable_route(message):
197+
assert not mission_chat.conversation_question(message)

‎tests/test_v100_live_dashboard.py‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ class Element {
181181
program += "\nrender(sample);if(nodes.actors.children.length!==2||!nodes.goals.textContent.includes('Operator goal')||nodes.events.children.length!==1||nodes['agent-grid'].children.length!==1)throw Error('Live renderer failed');\n"
182182
program += """
183183
const textNode={nodeType:3,nodeName:'#text',nodeValue:'previous'};const reader={nodeType:1,nodeName:'PRE',childNodes:[textNode],scrollTop:77};const freshReader={nodeType:1,nodeName:'PRE',childNodes:[{nodeType:3,nodeName:'#text',nodeValue:'updated'}]};updateNode(reader,freshReader);if(reader.childNodes[0]!==textNode||textNode.nodeValue!=='updated'||reader.scrollTop!==77)throw Error('Reading node replaced');
184+
latestInference={events:[{time:'2026-10-09T15:30:00Z'}],agents:[{label:'Live reader',stream_output:'fresh answer'}]};render(sample);if(!nodes['agent-grid'].children[0].children[0].children[0].textContent.includes('Live reader'))throw Error('Stale snapshot replaced live output');
184185
const old={dataset:{key:'reader'},open:false};let disclosures=[old];document.querySelectorAll=()=>disclosures;rememberDisclosures();
185186
const next={dataset:{key:'reader'},open:true};disclosures=[next];restoreDisclosures();if(next.open)throw Error('User-closed detail reopened');
186187
next.open=true;rememberDisclosures();const newer={dataset:{key:'reader'},open:false};disclosures=[newer];restoreDisclosures();if(!newer.open)throw Error('User-open detail lost');
@@ -286,3 +287,54 @@ def test_operator_opt_in_records_returned_local_trace_and_preserves_public_outpu
286287
agent = live_status.agent_views(events, [])[0]
287288
assert agent["returned_trace"] == trace
288289
assert "Public answer" in agent["declaration"]
290+
291+
292+
def test_transaction_retains_reading_container_and_scroll_across_updates():
293+
import shutil
294+
import subprocess
295+
296+
if not shutil.which("node"):
297+
pytest.skip("Node unavailable")
298+
program = r"""
299+
class Text {
300+
constructor(value){this.nodeType=3;this.nodeName='#text';this.nodeValue=value;this.parentNode=null}
301+
cloneNode(){return new Text(this.nodeValue)}
302+
remove(){if(this.parentNode){const p=this.parentNode;p.childNodes.splice(p.childNodes.indexOf(this),1);this.parentNode=null}}
303+
replaceWith(n){const p=this.parentNode;p.insertBefore(n,this);this.remove()}
304+
}
305+
class Element extends Text {
306+
constructor(tag){super('');this.nodeType=1;this.nodeName=tag.toUpperCase();this.childNodes=[];this.dataset={};this.style={};this.attrs={};this.scrollTop=0;this.scrollLeft=0}
307+
get children(){return this.childNodes.filter(n=>n.nodeType===1)}
308+
get attributes(){return Object.entries(this.attrs).map(([name,value])=>({name,value}))}
309+
get textContent(){return this.childNodes.map(n=>n.nodeType===3?n.nodeValue:n.textContent).join('')}
310+
set textContent(v){this.replaceChildren(new Text(String(v)))}
311+
append(...items){for(const n of items)this.insertBefore(typeof n==='string'?new Text(n):n,null)}
312+
insertBefore(n,before){n.remove();const i=before?this.childNodes.indexOf(before):this.childNodes.length;this.childNodes.splice(i,0,n);n.parentNode=this}
313+
replaceChildren(...items){for(const n of [...this.childNodes])n.remove();this.append(...items)}
314+
after(n){const p=this.parentNode;p.insertBefore(n,p.childNodes[p.childNodes.indexOf(this)+1]||null)}
315+
setAttribute(k,v){this.attrs[k]=String(v)}
316+
getAttribute(k){return this.attrs[k]??null}
317+
hasAttribute(k){return k in this.attrs}
318+
removeAttribute(k){delete this.attrs[k]}
319+
querySelectorAll(selector){const all=[];function visit(n){for(const c of n.children||[]){all.push(c);visit(c)}}visit(this);return all.filter(n=>selector[0]==='#'?n.id===selector.slice(1):selector==='pre'?n.nodeName==='PRE':selector==='[data-scroll-key]'?n.dataset.scrollKey:selector==='details[data-key]'?n.nodeName==='DETAILS'&&n.dataset.key:false)}
320+
querySelector(s){return this.querySelectorAll(s)[0]||null}
321+
cloneNode(deep){const n=new Element(this.nodeName);n.id=this.id;n.dataset={...this.dataset};n.attrs={...this.attrs};n.style={...this.style};n.open=this.open;if(deep)n.append(...this.childNodes.map(c=>c.cloneNode(true)));return n}
322+
}
323+
const body=new Element('body');const document={body,getElementById:id=>body.querySelector('#'+id),querySelectorAll:s=>body.querySelectorAll(s),createElement:t=>new Element(t),createElementNS:(ns,t)=>new Element(t),createTextNode:t=>new Text(t)};
324+
const window={scrollX:0,scrollY:400,scrollTo(x,y){this.scrollX=x;this.scrollY=y}};
325+
const location={reload(){throw Error('Unexpected reload')}};const setInterval=()=>{};const fetch=()=>new Promise(()=>{});
326+
"""
327+
for identity in dashboard_layout.REQUIRED_IDS:
328+
program += f"{{const n=new Element('div');n.id={json.dumps(identity)};body.append(n)}}\n"
329+
program += dashboard_layout.APP_SCRIPT
330+
program += r"""
331+
const readingCard=new Element('article');readingCard.dataset.scrollKey='gpu';readingCard.scrollTop=140;
332+
const detail=new Element('details');detail.dataset.key='gpu-proof';detail.open=true;
333+
const summary=new Element('summary');summary.textContent='Evidence';const pre=new Element('pre');pre.id='probe';pre.textContent='old output';pre.scrollTop=73;
334+
detail.append(summary,pre);readingCard.append(detail);body.append(readingCard);const originalText=pre.childNodes[0];
335+
for(let i=0;i<4;i++)stablePaint(()=>{$('probe').textContent='new output '+i});
336+
if(document.getElementById('probe')!==pre||pre.childNodes[0]!==originalText||pre.textContent!=='new output 3')throw Error('Reader DOM identity lost');
337+
if(!detail.open||readingCard.scrollTop!==140||pre.scrollTop!==73||window.scrollY!==400)throw Error('Reading position lost');
338+
"""
339+
result = subprocess.run(["node", "-"], input=program, text=True, capture_output=True, timeout=5)
340+
assert result.returncode == 0, result.stderr

‎tools/upgrade-v100-retention.sh‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ PY
4747
uv --no-config pip install --python "$PY" --no-deps --reinstall-package rlms \
4848
"rlms @ git+https://github.com/Mantiee/rlm.git@$REV"
4949
uv --no-config pip check --python "$PY"
50-
uv --no-config pip freeze --python "$PY" > "$AI_V100_ROOT/research/requirements.continual.v10054.txt"
50+
uv --no-config pip freeze --python "$PY" > "$AI_V100_ROOT/research/requirements.continual.v10055.txt"
5151

5252
"$PY" <<'PY'
5353
import json, os

0 commit comments

Comments
 (0)