Skip to content

Commit 41de365

Browse files
committed
v62: resume income analysis after protected serving startup, bound research retries and scope live evidence
1 parent 8ce50b1 commit 41de365

18 files changed

Lines changed: 489 additions & 32 deletions

‎SYNTA_RECOVERY.md‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,42 @@ Inspect `research/income-work/status.json`, its source-linked dossiers,
115115
`research/paper/fee-source-status.json` for measured execution. Feasibility
116116
estimates are neither real income nor training labels. Actual master training
117117
and promotion still need independently verified outcomes and all quality gates.
118+
119+
## v62: recover the supplied startup race without discarding evidence
120+
121+
The uploaded v61 report shows an income analysis hitting `/proc/515286/stat`
122+
after that old server exited. Source acquisition had succeeded. This is a
123+
serving readiness failure, not a failed income experiment.
124+
125+
- `income_work.accepted_master` waits for current-run startup readiness and
126+
requires the protected process/model receipt before generation. Missing process,
127+
receipt or endpoint becomes an explicit deferral. An identity mismatch remains
128+
a rejection. No candidate or disabled Windows RTX is substituted.
129+
- `income_work.execute` resumes a pending dossier under the same goal for up to
130+
24 hours without refetching or counting its source twice. Startup retries use
131+
30-120-second backoff for six attempts, then the normal ten-minute period.
132+
Analysis attempts are separately limited to three; failures stay archived.
133+
Goal changes during generation block registration under the new goal.
134+
- `drones.finish` changes only the next due time for these income retries;
135+
the configured periodic interval is retained. The waiting job releases its
136+
worker slot rather than sleeping inside it.
137+
- `research_policy.apply` cannot expand the bounded resident client's 1024-token
138+
ceiling or enable its thinking mode. Ordinary research policy remains separate.
139+
- The research audit rejects actual-labor income comparisons against simulated
140+
paper returns, recognizes the supplied Prolific/microtask variants, and flags
141+
the same advisory test under paraphrased hypotheses. Lexical checking is not
142+
semantic proof. Researchers receive the last four rejected tests and their
143+
reasons so they can revise them. Historical migration cannot replace the
144+
latest live audit.
145+
- New worker notes carry the originating mission run. Prior-run and migrated
146+
historical notes remain archived and are excluded from the live ideas list.
147+
- Cached official baselines publish finished progress for the current run.
148+
Complete current-run reports take precedence over stale global counters;
149+
previous-run counters are presented as history, never current computation.
150+
- `mission-report` includes owned CPU dispatch gates and the archived fee-source
151+
blocker. Dispatch is goal-attributed and rechecks promptly after a goal change.
152+
No training is forced with unverified labels or inadequate independent splits.
153+
154+
This release does not certify fees unavailable from the actual retrieved page,
155+
income, demand, novelty, CUDA utilization or browser behavior on the operator's
156+
machines. The existing Windows CPU limit and disabled RTX preference are retained.

‎SYNTA_REQUIREMENTS_AUDIT.md‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Synta: requirement and verification audit, v61
1+
# Synta: requirement and verification audit, v62
22

33
This audits the visible conversation. "Implemented" means code exists and the
44
listed checks pass, not that it has run successfully on the operator's computers.
@@ -100,3 +100,19 @@ and registered proposal when analysis succeeds. A blocked source/analysis has
100100
its own receipt. This is not autonomous financial execution, a profitable strategy,
101101
a security scan, a sale or universal task learning. See SYNTA_RECOVERY.md for
102102
exact modules and runtime artifact paths.
103+
104+
## v62 supplied-report regressions
105+
106+
Protected native startup defers income analysis without discarding the source;
107+
pending analysis resumes without counting another fetch. Missing PID, startup
108+
backoff, unchanged scheduling interval, bounded policy, invalid-analysis rotation,
109+
paper-vs-earned-income rejection, advisory-test repeats and benchmark run scoping
110+
have deterministic regression tests. Historical audits and notes are separated
111+
from current work. CPU training admission and fee-page blockers are visible.
112+
These are code/transport verifications, not a claim of verified income or hardware
113+
execution. See the v62 section of SYNTA_RECOVERY.md for exact limits.
114+
115+
Final v62 verification: 1098 tests passed, 65 skipped, one existing PEFT warning;
116+
no deselections. Ruff for Python/scripts/tests excluding the unchanged notebook,
117+
upgrade shell syntax and diff whitespace checks pass. No Windows, CUDA or
118+
operator-browser runtime acceptance is asserted by this result.

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

‎rlm/v100/agent.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ def native_turn(
5555
)
5656
prompt_tokens = client.count_text(template["prompt"], parse_special=True) + 1
5757
max_tokens = client.sampling_args.get("max_tokens", 512)
58+
host_ceiling = getattr(client, "research_token_ceiling", None)
59+
if host_ceiling is not None:
60+
max_tokens = min(max_tokens, host_ceiling)
5861
sampling = {
5962
key: client.sampling_args[key]
6063
for key in ("temperature", "seed", "top_p", "top_k")
@@ -82,6 +85,8 @@ def native_turn(
8285
):
8386
raise ValueError("Research retry output limit must be 1-8192 tokens")
8487
ceiling = min(retry_output_limit or max_tokens, client.context_window - prompt_tokens)
88+
if host_ceiling is not None:
89+
ceiling = min(ceiling, host_ceiling)
8590
attempts, completion_tokens = 0, 0
8691
while True:
8792
attempts += 1

‎rlm/v100/dashboard_snapshot.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ def supplement(root: Path, mission: dict, report: dict, errors: list) -> dict:
1212
from rlm.v100.planning import read
1313

1414
result = dict(report)
15+
from rlm.v100.public_benchmarks import progress_for_run
16+
17+
benchmark = progress_for_run(root, Path(mission["run"]) if mission.get("run") else None)
18+
if benchmark.get("report") or benchmark.get("historical") or "official_benchmark" in result:
19+
result["official_benchmark"] = benchmark
1520
for key, filename in (
1621
("drones", "research/drones-status.json"),
1722
("external_compute", "research/compute-status.json"),

‎rlm/v100/drones.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,10 @@ def finish(root: Path, identity: str, value: dict) -> None:
220220
with connect(root) as db:
221221
row = db.execute("SELECT * FROM jobs WHERE id=?", (identity,)).fetchone()
222222
interval = row["interval"]
223+
delay = interval
224+
retry = value.get("retry_after_seconds")
225+
if row["kind"] == "income" and interval and type(retry) is int and 15 <= retry <= 600:
226+
delay = retry
223227
state = (
224228
"cancelled" if row["state"] == "cancelled" else "queued" if interval else "completed"
225229
)
@@ -254,12 +258,13 @@ def finish(root: Path, identity: str, value: dict) -> None:
254258
job_state["validation_error"] = value.get("detail", "Local validation failed")
255259
if retries < 3:
256260
state, interval = "queued", 60
261+
delay = interval
257262
else:
258263
job_state["state"] = "validation-deferred"
259264
atomic_json(source, job_state)
260265
db.execute(
261266
"UPDATE jobs SET state=?,due=?,updated=?,result=? WHERE id=?",
262-
(state, time.time() + interval, time.time(), json.dumps(value), identity),
267+
(state, time.time() + delay, time.time(), json.dumps(value), identity),
263268
)
264269
ActivityLog(root, row["branch"], "drone").write(
265270
"steps", "drone-result", {"id": identity, **value}

‎rlm/v100/goal_compute.py‎

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ def propose(root: Path, branch: str = "A") -> dict:
113113

114114
def tick(root: Path) -> None:
115115
from rlm.v100.distributed_compute import inspect
116+
from rlm.v100.goals import load_goal
117+
118+
goal_id = (load_goal(root) or {}).get("id")
116119

117120
preferences = root / "research/user-preferences.json"
118121
if not preferences.exists() or not json.loads(preferences.read_text()).get(
@@ -128,10 +131,16 @@ def tick(root: Path) -> None:
128131
except BlockingIOError:
129132
return
130133
previous = json.loads(status.read_text()) if status.exists() else {}
131-
if time.time() < previous.get("next_poll", 0):
134+
if previous.get("goal_id") == goal_id and time.time() < previous.get("next_poll", 0):
132135
return
133136
atomic_json(
134-
status, {"state": "checking", "updated": time.time(), "next_poll": time.time() + 300}
137+
status,
138+
{
139+
"state": "checking",
140+
"goal_id": goal_id,
141+
"updated": time.time(),
142+
"next_poll": time.time() + 300,
143+
},
135144
)
136145
try:
137146
resources = inspect(root)
@@ -157,7 +166,10 @@ def tick(root: Path) -> None:
157166
result["state"] = "no-new-data"
158167
except (OSError, ValueError, RuntimeError, KeyError) as error:
159168
result = {"state": "blocked", "reason": str(error)[:400]}
160-
atomic_json(status, {**result, "updated": time.time(), "next_poll": time.time() + 300})
169+
atomic_json(
170+
status,
171+
{**result, "goal_id": goal_id, "updated": time.time(), "next_poll": time.time() + 300},
172+
)
161173

162174

163175
def verify_job(root: Path, job: dict) -> None:

‎rlm/v100/income_work.py‎

Lines changed: 85 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727
)
2828

2929

30+
class MasterNotReady(RuntimeError):
31+
"""Transient serving startup, shutdown or unavailable protected receipt."""
32+
33+
3034
def accepted_master(root: Path, branch: str):
3135
"""Reuse a protected serving master, never launch a model or use a candidate."""
3236
from rlm.v100.competition import helper_client
@@ -36,22 +40,37 @@ def accepted_master(root: Path, branch: str):
3640
run = Path(active["run"]).resolve()
3741
if not run.is_relative_to((root / "research/mission").resolve()):
3842
raise ValueError("Mission escaped its owned directory")
39-
state = json.loads((run / "status.json").read_text())
43+
status_path = run / "status.json"
44+
state = json.loads(status_path.read_text()) if status_path.exists() else {}
4045
paths = [run / "serving-active.json", run / "learning/live.json"]
4146
paths.append(run / f"profile-{state.get('context_window')}.json")
4247
path = next((candidate for candidate in paths if candidate.is_file()), None)
4348
if path is None:
44-
raise RuntimeError("Accepted master profile not ready; collected evidence retained")
49+
raise MasterNotReady("Accepted master profile not ready; collected evidence retained")
50+
startup = run / f"server-{state.get('context_window')}.startup.json"
51+
# The initial server is stopped when continuous learning starts its own
52+
# protected server. Its old startup file must not block the live profile.
53+
if (
54+
path.name.startswith("profile-")
55+
and startup.exists()
56+
and json.loads(startup.read_text()).get("stage") != "ready"
57+
):
58+
raise MasterNotReady("Accepted V100 serving startup not ready; evidence retained")
4559
profile = load_profile(path, root)
4660
client = helper_client(profile, root, branch)
4761
client.timeout = min(10, client.timeout)
4862
# Full protected receipt and endpoint check precedes any generation. A
4963
# candidate temporarily occupying the same endpoint is never used.
50-
assert_served_expert(client, profile, root)
64+
try:
65+
assert_served_expert(client, profile, root)
66+
except (FileNotFoundError, ProcessLookupError, requests.RequestException) as error:
67+
raise MasterNotReady("Accepted serving process or endpoint not ready") from error
5168
client.timeout = 40
5269
client.request_deadline = time.monotonic() + 60
5370
client.enable_thinking = False
5471
client.sampling_args = dict(client.sampling_args) | {"max_tokens": 1024}
72+
client.research_token_ceiling = 1024
73+
client.research_thinking_allowed = False
5574
client.activity_actor = "income-research"
5675
return client
5776

@@ -86,52 +105,90 @@ def execute(root: Path, job: dict) -> dict:
86105
path = directory / "status.json"
87106
previous = json.loads(path.read_text()) if path.exists() else {}
88107
same_goal = previous.get("goal_id") == goal["id"]
108+
pending = None
109+
if same_goal and previous.get("dossier"):
110+
candidate = Path(previous["dossier"]).resolve()
111+
if candidate.is_relative_to((directory / "dossiers").resolve()) and candidate.is_file():
112+
saved = json.loads(candidate.read_text())
113+
if (
114+
saved.get("goal_id") == goal["id"]
115+
and saved.get("analysis") is None
116+
and previous.get("state")
117+
in (
118+
"waiting for accepted master",
119+
"evidence collected; analysis blocked",
120+
)
121+
and saved.get("analysis_attempts", 0) < 3
122+
and 0 <= time.time() - saved["source"]["acquired"] <= 86400
123+
):
124+
pending = saved
89125
index = previous.get("next_source", 0) if same_goal else 0
90126
domain, url = STARTING_SOURCES[index % len(STARTING_SOURCES)]
127+
if pending:
128+
domain, url = previous["domain"], previous["url"]
91129
value = {
92130
"goal_id": goal["id"],
93131
"job_id": job["id"],
94132
"updated": time.time(),
95133
"state": "collecting primary terms",
96134
"domain": domain,
97135
"url": url,
98-
"next_source": index + 1,
136+
"next_source": index if pending else index + 1,
99137
"sources_collected": previous.get("sources_collected", 0) if same_goal else 0,
100138
"proposals_registered": previous.get("proposals_registered", 0) if same_goal else 0,
101139
"actual_income_pln": None,
102140
"scope": "Public-terms research and preparation only; no account, application, sale, security scan or payment executed",
103141
}
142+
if pending:
143+
value["readiness_attempts"] = previous.get("readiness_attempts", 0)
104144
atomic_json(path, value)
105145
journal = ActivityLog(root, job["branch"], "income-research")
106146
step = journal.write(
107147
"tools",
108148
"tool-start",
109-
{"tool": "observe_goal_source", "arguments": {"url": url, "field": ""}},
149+
{
150+
"tool": "read_income_dossier" if pending else "observe_goal_source",
151+
"arguments": {"path": previous["dossier"]} if pending else {"url": url, "field": ""},
152+
},
110153
)
111154
try:
112-
source = observe(root, url)
155+
source = pending["source"] if pending else observe(root, url)
113156
except (ValueError, OSError, RuntimeError, requests.RequestException) as error:
114157
value.update(state="source unavailable", error=str(error)[:400], updated=time.time())
115158
atomic_json(path, value)
116159
journal.write("errors", "source-unavailable", value, step_id=step)
117160
return {**value, "status": "failed"}
118161
journal.write(
119-
"tools", "tool-result", {"tool": "observe_goal_source", "result": source}, step_id=step
162+
"tools",
163+
"tool-result",
164+
{
165+
"tool": "read_income_dossier" if pending else "observe_goal_source",
166+
"result": source,
167+
"scope": "Archived evidence reused; no new fetch" if pending else "New source fetch",
168+
},
169+
step_id=step,
120170
)
121171
value.update(
122172
state="evidence collected; analysis pending",
123173
evidence_id=source["id"],
124-
sources_collected=value["sources_collected"] + 1,
174+
sources_collected=value["sources_collected"] + (0 if pending else 1),
125175
)
126176
dossier = directory / "dossiers" / (source["id"] + ".json")
127-
atomic_json(
128-
dossier,
129-
{"goal_id": goal["id"], "source": source, "analysis": None, "scope": value["scope"]},
130-
)
177+
saved = pending or {
178+
"goal_id": goal["id"],
179+
"source": source,
180+
"analysis": None,
181+
"scope": value["scope"],
182+
"analysis_attempts": 0,
183+
}
184+
atomic_json(dossier, saved)
131185
value["dossier"] = str(dossier)
132186
atomic_json(path, value)
187+
attempts_before = saved.get("analysis_attempts", 0)
133188
try:
134189
client = accepted_master(root, job["branch"])
190+
saved["analysis_attempts"] = saved.get("analysis_attempts", 0) + 1
191+
atomic_json(dossier, saved)
135192
response = native_turn(
136193
client,
137194
[
@@ -165,6 +222,8 @@ def execute(root: Path, job: dict) -> dict:
165222
raise ValueError(
166223
"No accepted work or payment receipt; conservative income lower bound must be zero"
167224
)
225+
if (load_goal(root) or {}).get("id") != goal["id"]:
226+
raise ValueError("Goal changed during analysis; proposal retained but not registered")
168227
row = register(root, job["branch"], specification)
169228
atomic_json(
170229
dossier,
@@ -175,8 +234,22 @@ def execute(root: Path, job: dict) -> dict:
175234
candidate_id=row["id"],
176235
proposals_registered=value["proposals_registered"] + 1,
177236
)
237+
except MasterNotReady as error:
238+
attempts = value.get("readiness_attempts", 0) + 1
239+
value.update(
240+
state="waiting for accepted master",
241+
error=str(error)[:400],
242+
readiness_attempts=attempts,
243+
retry_after_seconds=min(120, 15 * 2 ** min(attempts, 3)) if attempts <= 6 else 600,
244+
)
245+
journal.write("steps", "income-analysis-deferred", value)
178246
except (ValueError, OSError, RuntimeError, requests.RequestException) as error:
247+
saved["analysis_attempts"] = max(attempts_before + 1, saved.get("analysis_attempts", 0))
248+
saved["analysis_error"] = str(error)[:400]
249+
atomic_json(dossier, saved)
179250
value.update(state="evidence collected; analysis blocked", error=str(error)[:400])
251+
if saved.get("analysis_attempts", 0) < 3:
252+
value["retry_after_seconds"] = 120
180253
journal.write("errors", "income-analysis-blocked", value)
181254
value["updated"] = time.time()
182255
atomic_json(path, value)

‎rlm/v100/mission.py‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,13 @@ def run(root: Path, profile: dict, directory: Path) -> None:
363363
try:
364364
cached_public = reusable_public(root, serving)
365365
if cached_public:
366-
atomic_json(public_path, json.loads(cached_public.read_text()))
366+
from rlm.v100.public_benchmarks import write_progress
367+
368+
reused = json.loads(cached_public.read_text())
369+
atomic_json(public_path, reused)
370+
write_progress(
371+
root, public_path, reused, len(reused["cases"]), "finished"
372+
)
367373
atomic_json(
368374
directory / "public-baseline-reused.json",
369375
{"source": str(cached_public), "sha256": file_hash(cached_public)},

0 commit comments

Comments
 (0)