Skip to content

Commit 2b2aa2e

Browse files
committed
v46: show evidenced agent activity and readable chat queue progress
1 parent a8713c3 commit 2b2aa2e

8 files changed

Lines changed: 261 additions & 18 deletions

‎V100_AUDIT.md‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,3 +401,25 @@ history isolation, host receipts, rejection before desktop execution, archive/re
401401
recovery, preservation of later plans and refusal to repair a running mission.
402402
Ruff, formatting, shell syntax and diff whitespace checks passed. Hardware/LAN
403403
allocation and useful execution still require the operator's deployment receipts.
404+
405+
## v100.46 observed agent activity and readable chat
406+
407+
The trusted live renderer adds per-agent cards for observed state, assigned task,
408+
inference device/model, declared conclusion or next step, last tool result, recorded
409+
time and evidence file. Declarations remain explicitly unverified; no private
410+
reasoning/thinking fields are selected. Missing declarations stay absent. The read
411+
budget remains two daily tails of 128 KiB, 40 events and 16 recent jobs. Hardware
412+
readiness is not attributed as agent execution. Host CSS improves old saved layouts
413+
without replacing operator/master guest HTML. The same validated guest editor remains
414+
available. Browsers already open during installation need a full page refresh.
415+
416+
Terminal chat separates user, master and action receipts, displays queue position
417+
or active processing elapsed time every 15 seconds, and exits cleanly on Ctrl+C
418+
without cancelling mission work. Identical pending messages reuse their request ID.
419+
These UI changes do not raise helper pacing/resource caps or prove useful allocation.
420+
421+
Validation: 972 tests passed, 65 skipped, 1 owned-worker integration deselected and
422+
one PEFT configuration warning. Tests execute the trusted renderer on an old layout,
423+
cover pending-request deduplication, queue/processing labels, clean interruption,
424+
source attribution and omission of private reasoning fields. Ruff, shell syntax
425+
and diff whitespace checks passed. No V100/Windows/LAN execution is certified here.

‎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.45"
6+
version = "0.1.3+v100.46"
77
description = "Recursive Language Models."
88
readme = "README.md"
99
license = "MIT"

‎rlm/v100/dashboard_layout.py‎

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@
1919
<section><h2>Paper A/B</h2><pre id="paper"></pre><h3>Blokady</h3><pre id="blockers"></pre></section>
2020
<section><h2>Raporty HTML z wykresami i pliki wyników</h2><p class="muted">Raporty są zapisanymi migawkami, mają własny czas i zakres.</p><div id="reports"></div></section>
2121
<section><h2>Co teraz pracuje</h2><div id="actors" class="grid"></div><h3>Cele i plany</h3><pre id="goals"></pre><h3>Ostatnie zdarzenia</h3><div id="events"></div></section>
22-
<section><h2>Pomocnicy, GUI i benchmark oficjalny</h2><pre id="workers"></pre></section>
23-
<section><h2>Aktualny log kontrolera</h2><pre id="log"></pre></section>
22+
<details><summary>Pomocnicy, GUI i benchmark oficjalny - dane źródłowe</summary><pre id="workers"></pre></details>
23+
<details><summary>Aktualny log kontrolera</summary><pre id="log"></pre></details>
2424
<details><summary>Dowody treningu i ostatnie błędy</summary><pre id="evidence"></pre></details>
2525
<script>
2626
const $=id=>document.getElementById(id), fmt=x=>x===null||x===undefined?'brak potwierdzonych danych':String(x), pct=x=>typeof x==='number'&&Number.isFinite(x)?(x*100).toFixed(2)+'%':'brak danych';
2727
const layoutVersion='__LAYOUT_SHA__';
2828
function card(label,value){const node=document.createElement('div');node.className='card';const title=document.createElement('small');title.textContent=label;const strong=document.createElement('strong');strong.className='value';strong.textContent=fmt(value);node.append(title,strong);$('cards').append(node)}
2929
function workView(data){
30+
agentView(data);
3031
for(const id of ['actors','goals','events']){if(!$(id)){const node=document.createElement(id==='goals'?'pre':'div');node.id=id;if(id==='actors')node.className='grid';$('cards').after(node)}}
3132
$('actors').replaceChildren();
3233
for(const a of data.live?.actors||[]){
@@ -39,6 +40,13 @@
3940
for(const j of a.jobs||[]){const p=document.createElement('p');p.textContent=[j.kind,j.branch,j.state||j.phase,j.id,j.assignment].filter(Boolean).join(' | ');node.append(p)}
4041
const proof=document.createElement('details'),summary=document.createElement('summary'),raw=document.createElement('pre');summary.textContent='Dane źródłowe';raw.textContent=JSON.stringify(a,null,2);proof.append(summary,raw);node.append(proof);$('actors').append(node)
4142
}
43+
function agentView(data){
44+
let panel=$('agent-panel');if(!panel){panel=document.createElement('section');panel.id='agent-panel';const title=document.createElement('h2');title.textContent='Agent activity - tasks, decisions and evidence';const note=document.createElement('p');note.className='muted';note.textContent='Public conclusions and observed tool results. Declarations are not proof of execution. Internal reasoning is not displayed.';const grid=document.createElement('div');grid.id='agent-grid';grid.className='grid';panel.append(title,note,grid);$('cards').after(panel)}
45+
let grid=$('agent-grid');grid.replaceChildren();
46+
for(const a of data.live?.agents||[]){const node=document.createElement('article');node.className='card agent';const heading=document.createElement('h3');heading.textContent=a.label;node.append(heading);
47+
for(const [label,value] of [['Observed state',a.state],['Inference device',a.device],['Model',a.model],['Assigned task',a.task],['Tool',a.tool],['Declared conclusion / next step (unverified)',a.declaration],['Last observed result',a.result],['Recorded at',typeof a.updated==='number'?new Date(a.updated*1000).toLocaleString():a.updated],['Evidence file',a.source]]){if(value){const p=document.createElement('p'),b=document.createElement('strong');b.textContent=label+': ';p.append(b,document.createTextNode(String(value)));node.append(p)}}grid.append(node)}
48+
if(!grid.children.length){const p=document.createElement('p');p.textContent='No recent agent evidence. No activity is inferred from device readiness.';grid.append(p)}
49+
}
4250
const plans=data.live?.goals||{};$('goals').textContent=[['long','Cel długoterminowy'],['mid','Plan średnioterminowy'],['short','Plan krótkoterminowy']].map(([key,label])=>label+': '+(plans[key]?.text||'brak planu')+(plans[key]?.needs_replanning?' (wymaga przeplanowania)':'')).join('\n\n');
4351
$('events').replaceChildren();for(const e of [...(data.live?.events||[])].reverse().slice(0,20)){const p=document.createElement('p');p.textContent=[e.time,e.actor,e.kind,e.tool,e.detail].filter(Boolean).join(' | ');$('events').append(p)}
4452
}
@@ -62,6 +70,21 @@
6270
GUEST_TEMPLATE = "/workspace/dashboard/index.html"
6371
REQUIRED_IDS = set(re.findall(r"\$\('([a-z]+)'\)", APP_SCRIPT)) - {"actors", "goals", "events"}
6472

73+
HOST_STYLE = """
74+
:root{color-scheme:dark;--line:#2b4058;--muted:#a6b7cc}
75+
body{max-width:1440px;margin:auto;padding:28px;background:#0b1220;color:#e7eef9;font:15px/1.6 system-ui}
76+
h1{font-size:32px;letter-spacing:-1px;margin-bottom:8px}h2{font-size:21px;margin-top:0}h3{font-size:16px;overflow-wrap:anywhere}
77+
section,details{background:#111e30;border:1px solid var(--line);border-radius:16px;padding:22px;margin:20px 0}
78+
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px}
79+
.card{background:#16263b;border:1px solid var(--line);border-radius:12px;padding:18px;margin:0;min-width:0}
80+
.agent{border-top:3px solid #75c5ae}.agent p{font-size:13px;overflow-wrap:anywhere}.agent strong{color:#9ac5ff}
81+
#agent-panel{border-color:#487087}#actors .card{border-top:3px solid #82aef9}.muted,small{color:var(--muted)}
82+
.value{font-size:24px}pre{background:#0c1726;border-radius:8px;padding:14px;font-size:12px;max-height:280px;overflow:auto;white-space:pre-wrap;overflow-wrap:anywhere}
83+
summary{cursor:pointer;color:#9ac5ff}#events p{padding:10px 14px;border-left:2px solid #487087;background:#0c1726;font-size:12px;overflow-wrap:anywhere}
84+
button{background:#365c87;padding:10px 18px;border-radius:8px}progress{accent-color:#75c5ae}td,th{padding:12px;border-bottom:1px solid var(--line)}
85+
@media(max-width:650px){body{padding:12px}.grid{grid-template-columns:1fr}section,details{padding:14px}h1{font-size:26px}}
86+
"""
87+
6588

6689
def validate_css(value):
6790
value = re.sub(r"/\*.*?\*/", "", value, flags=re.S).lower()
@@ -136,7 +159,13 @@ def validate_template(content):
136159

137160

138161
def render_template(content, identity):
139-
script = "<script>" + APP_SCRIPT.replace("__LAYOUT_SHA__", identity) + "</script>"
162+
script = (
163+
"<style>"
164+
+ HOST_STYLE
165+
+ "</style><script>"
166+
+ APP_SCRIPT.replace("__LAYOUT_SHA__", identity)
167+
+ "</script>"
168+
)
140169
return re.sub(
141170
r"</html\s*>\s*\Z", lambda match: script + match.group(), content, count=1, flags=re.I
142171
)

‎rlm/v100/live_status.py‎

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,27 @@
77
import requests
88

99

10+
def public_summary(value: dict) -> str:
11+
"""Select public conclusions only; never expose reasoning/thinking token fields."""
12+
if not isinstance(value, dict):
13+
return ""
14+
parts = []
15+
for key in (
16+
"answer",
17+
"hypothesis",
18+
"rationale",
19+
"next_test",
20+
"confidence",
21+
"status",
22+
"error",
23+
"detail",
24+
"report",
25+
):
26+
if isinstance(value.get(key), (str, int, float)):
27+
parts.append(f"{key}: {str(value[key])[:360]}")
28+
return " | ".join(parts)[:800]
29+
30+
1031
def recent_events(root: Path, limit: int = 40) -> list[dict]:
1132
paths = sorted((root / "research/logs/activity").glob("*/timeline.jsonl"))[-2:]
1233
events = []
@@ -34,11 +55,64 @@ def recent_events(root: Path, limit: int = 40) -> list[dict]:
3455
"kind": row.get("kind"),
3556
"tool": payload.get("tool"),
3657
"detail": str(payload.get("error", payload.get("status", "")))[:250],
58+
"summary": public_summary(payload.get("content", {}))
59+
or public_summary(payload.get("result", {}))
60+
or public_summary(payload),
61+
"category": row.get("category"),
62+
"source": str(path),
63+
"device": payload.get("device"),
64+
"model": payload.get("model"),
65+
"task": " | ".join(
66+
f"{key}: {str(payload.get('arguments', {}).get(key))[:200]}"
67+
for key in ("query", "url", "brief", "kind", "action")
68+
if isinstance(payload.get("arguments"), dict)
69+
and key in payload["arguments"]
70+
),
3771
}
3872
)
3973
return events[-limit:]
4074

4175

76+
def agent_views(events: list[dict], jobs: list[dict]) -> list[dict]:
77+
"""Observed agent activity plus labelled declarations, not inferred intentions."""
78+
views = {}
79+
for event in events:
80+
key = (event.get("branch"), event.get("actor"))
81+
view = views.setdefault(
82+
key,
83+
{"label": " / ".join(str(v) for v in key if v), "declaration": "", "result": ""},
84+
)
85+
view.update(
86+
updated=event.get("time"),
87+
state=event.get("kind"),
88+
tool=event.get("tool"),
89+
source=event.get("source"),
90+
)
91+
if event.get("task"):
92+
view["task"] = event["task"]
93+
if event.get("device"):
94+
view["device"] = event["device"]
95+
view["model"] = event.get("model")
96+
if event.get("category") == "decisions" and event.get("summary"):
97+
view["declaration"] = event["summary"]
98+
elif event.get("summary"):
99+
view["result"] = event["summary"]
100+
result = list(views.values())[-12:]
101+
for job in jobs[:16]:
102+
result.append(
103+
{
104+
"label": f"{job['branch']} / {job['kind']} / {job['id']}",
105+
"state": job["state"],
106+
"task": job.get("assignment", ""),
107+
"updated": job.get("updated"),
108+
"result": public_summary(job.get("result") or {}),
109+
"declaration": "",
110+
"source": "research/state/drones.sqlite3",
111+
}
112+
)
113+
return result
114+
115+
42116
def current_chat(root: Path, mission: dict) -> dict:
43117
path = root / "research/state/chat-active.json"
44118
if not path.exists():
@@ -127,7 +201,7 @@ def snapshot(root: Path, mission: dict, report: dict, gpu: dict) -> dict:
127201
"label": "RTX helper",
128202
"state": "unknown",
129203
"jobs": [j for j in active if j["kind"] in ("researcher", "critic", "benchmark")],
130-
"detail": "Utilization and power unknown; request pacing is not a hard GPU cap",
204+
"detail": "Jobs eligible for helper; actual inference device is recorded in agent events. Utilization and power unknown; request pacing is not a hard GPU cap",
131205
"stale": True,
132206
}
133207
from rlm.v100.common import load_profile
@@ -172,10 +246,12 @@ def snapshot(root: Path, mission: dict, report: dict, gpu: dict) -> dict:
172246
if remaining and not rtx["stale"]:
173247
rtx["state"] = "cooldown"
174248
actors.append(rtx)
249+
events = recent_events(root)
175250
return {
176251
"observed_at": now,
177252
"actors": actors,
178253
"goals": read(root),
179-
"events": recent_events(root),
254+
"events": events,
255+
"agents": agent_views(events, jobs),
180256
"chat": chat,
181257
}

‎rlm/v100/mission_chat.py‎

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ def submit(root: Path, message: str) -> str:
4444
identity = uuid.uuid4().hex
4545
with connect(root) as db:
4646
db.execute("BEGIN IMMEDIATE")
47+
pending = db.execute(
48+
"SELECT id FROM requests WHERE state='queued' AND message=? ORDER BY rowid LIMIT 1",
49+
(message,),
50+
).fetchone()
51+
if pending:
52+
return pending["id"]
4753
if db.execute("SELECT count(*) FROM requests WHERE state='queued'").fetchone()[0] >= 32:
4854
raise ValueError("Chat queue has 32 pending requests; let them complete first")
4955
db.execute(
@@ -56,9 +62,18 @@ def submit(root: Path, message: str) -> str:
5662
def inspect(root: Path, identity: str) -> dict:
5763
with connect(root) as db:
5864
row = db.execute("SELECT * FROM requests WHERE id=?", (identity,)).fetchone()
65+
position = (
66+
db.execute(
67+
"SELECT count(*) FROM requests WHERE state='queued' AND rowid<=(SELECT rowid FROM requests WHERE id=?)",
68+
(identity,),
69+
).fetchone()[0]
70+
if row is not None and row["state"] == "queued"
71+
else None
72+
)
5973
if row is None:
6074
raise ValueError("Unknown chat request")
6175
result = dict(row)
76+
result["queue_position"] = position
6277
if result["response"]:
6378
result["response"] = json.loads(result["response"])
6479
active_path = root / "research/state/chat-active.json"
@@ -583,21 +598,36 @@ def alongside(root: Path, directory: Path):
583598
accepted_cpu.close()
584599

585600

586-
def wait_reply(root: Path, identity: str, timeout: float = 120) -> dict:
601+
def wait_reply(root: Path, identity: str, timeout: float = 120, progress=None) -> dict:
587602
deadline = time.monotonic() + timeout
603+
next_update = time.monotonic()
588604
while time.monotonic() < deadline:
589605
result = inspect(root, identity)
590606
if result["state"] != "queued":
591607
return result
608+
if progress is not None and time.monotonic() >= next_update:
609+
progress(result)
610+
next_update = time.monotonic() + 15
592611
time.sleep(0.5)
593612
return inspect(root, identity)
594613

595614

615+
def waiting_label(result: dict) -> str:
616+
active = result.get("processing")
617+
if active:
618+
elapsed = max(0, round(time.time() - active["started"]))
619+
return f"Processing: {elapsed}s elapsed | budget {active.get('time_budget_seconds', '?')}s | request {result['id']}"
620+
text = f"Queued: position {result.get('queue_position', '?')} | request {result['id']}"
621+
if result.get("error"):
622+
text += " | last recorded issue: " + result["error"]
623+
return text
624+
625+
596626
def chat(root: Path, message: str | None = None) -> None:
597-
print("Czat misji. /exit kończy tylko czat. Podczas treningu lub gry polecenie może czekać.")
627+
print("\nV100 MASTER CHAT\n/exit closes chat only. /status reads mission status.\n")
598628
while True:
599629
try:
600-
text = message if message is not None else input("Ty> ")
630+
text = message if message is not None else input("\nYOU > ")
601631
except (EOFError, KeyboardInterrupt):
602632
break
603633
if text.strip() == "/exit":
@@ -609,19 +639,33 @@ def chat(root: Path, message: str | None = None) -> None:
609639
break
610640
continue
611641
identity = submit(root, text)
612-
print("Zapisano polecenie:", identity, flush=True)
613-
result = wait_reply(root, identity)
642+
print("\nREQUEST:", identity, flush=True)
643+
try:
644+
result = wait_reply(
645+
root, identity, progress=lambda row: print(waiting_label(row), flush=True)
646+
)
647+
except KeyboardInterrupt:
648+
print("\nChat closed. Request remains queued/processing; mission continues.")
649+
break
614650
if result["state"] == "completed":
615651
who = result["response"].get("responder", {})
616652
label = who.get("model", "controller")
617653
if who.get("delegated_while_master_busy"):
618654
label += " - zastępca, V100 zajęty"
619655
elif who.get("accepted_master_on_cpu"):
620656
label += " - zaakceptowany master na CPU"
621-
print(f"{label}>", result["response"]["answer"])
622-
print("Wykonane zmiany:", json.dumps(result["response"]["applied"], ensure_ascii=False))
657+
print(f"\nMASTER [{label}]\n\n{result['response']['answer']}\n")
658+
applied = result["response"]["applied"]
659+
print(
660+
"ACTION RECEIPTS:",
661+
json.dumps(applied, ensure_ascii=False) if applied else "No control changes",
662+
)
623663
else:
624-
print(json.dumps({k: result[k] for k in ("id", "state", "error")}, ensure_ascii=False))
625-
print("Odczyt: v100-continual chat-status", identity)
664+
print(
665+
waiting_label(result)
666+
if result["state"] == "queued"
667+
else f"FAILED: {result.get('error')}"
668+
)
669+
print("Read result: v100-continual chat-status", identity)
626670
if message is not None:
627671
break

0 commit comments

Comments
 (0)