Skip to content

Commit fd41659

Browse files
committed
v74: execute model-selected goal work and learn shadow forecasts from verified outcomes
Move income jobs beyond fixed commissioning sources into bounded model-selected tool experiments with persisted receipts and an explicit zero-real-capital / funded-paper contract. Expose web, guest and evidence tools to paper researchers, permit six-step experiments, bound inference time and fall back from remote transport outages to the protected accepted V100. Preserve identity failures and execution/resource gates. Enter the continuous loop without duplicate startup research. Add an audited CPU softmax shadow policy using causal past-only features and independently settled outcomes, including failed predictions. Deduplicate paired target commitments and score replay only with labels available before each commitment; compare uniform, flat and frequency baselines. Keep uniform references and separately commit prospective learned shadow tests. No Gemma promotion, real orders or verified income implied. Add profile-preserving v74 updater with archived policy transition to hypothetical funded research and zero real spending. Validation: 1275 passed, 65 skipped (existing PEFT warning); final baseline and execution checks 40 passed. Ruff, bash syntax and diff checks passed. Live hardware and profitable strategies not independently verified.
1 parent 00e4728 commit fd41659

19 files changed

Lines changed: 701 additions & 52 deletions

‎README.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11

22
---
33

4+
Synta v74 adds model-selected goal experiments after source commissioning, offline
5+
helper recovery and a CPU shadow forecast learner using audited future outcomes.
6+
Real capital is zero; existing PAPER balances remain usable. See [SYNTA_V74.md](SYNTA_V74.md).
7+
48
Synta v73 keeps paper observations running when the fee publication cannot be verified,
59
without extending expired fees or admitting fills. See [SYNTA_V73.md](SYNTA_V73.md).
610

‎SYNTA_V74.md‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Synta v74: continuous goal work and measured shadow learning
2+
3+
This release gives the accepted master executable tools and separates actual learning evidence from research prose. It does not certify income, optimal settings or universal internet automation.
4+
5+
## Changes
6+
7+
- `rlm/v100/income_work.py`: after the three commissioning sources, the accepted model selects its next source, domain and experiment using the named tools. Six tool steps and a 180-second inference deadline. Complete tool traces are saved under `research/income-work/experiments`; preparation and HTTP/process receipts are never recorded as earned income.
8+
- `rlm/v100/paper_agents.py`: researchers can inspect/open public web pages, prepare reviewed submissions, run guest experiments, inspect exact trade costs and consult the learned forecast policy. Real spendable capital is zero; the existing virtual paper portfolios are available for eligible funded tests. No resetting balances or weakening fee/quote/risk gates.
9+
- `rlm/v100/drones.py`: transport failure on the Windows helper uses the protected, accepted V100 master. A changed model identity still fails. Windows benchmarks remain specific to Windows.
10+
- `rlm/v100/research_tools.py`: explicitly bounded research tool sequences can use six steps instead of stopping after three. Existing tasks default to three; the maximum is eight. This is a capability budget, not keyword routing of user questions.
11+
- `rlm/v100/researchers.py`: requests have an overall 180-second inference budget unless their caller supplies a shorter one. The model is told to use measured evidence for optimization claims.
12+
- `rlm/v100/mission.py`: commissioning no longer repeats a long paper research round before entering the continuous research/training loop. Baselines and protected identity checks remain required.
13+
14+
## CPU shadow learning
15+
16+
`rlm/v100/goal_policy.py` trains a bounded softmax classifier using verified active-goal outcomes, including failures. Features use only past observations of the same numeric source. Replay scoring trains only on labels already available before a forecast was committed; overlapping horizons cannot leak future labels. Reference and shadow commitments on one target/horizon/threshold count as one training label. At most 512 archived commitments are used; history is bounded to eight observations. This is supervised online-style replay, separate from Gemma LoRA updates.
17+
18+
`rlm/v100/goal_observer.py` keeps the uniform A reference and commits a learned B shadow forecast on the same fresh observation. Both are independently settled by the existing host observer. The model can inspect learned probabilities through `suggest_goal_forecast`. Uniform probabilities are explicit cold-start/reference baselines, not master insight. No automatic trade execution follows these forecasts.
19+
20+
`research/goal-policy/status.json` reports trained outcomes, changed CPU weights and prequential Brier score against uniform, always-flat and prior-outcome-frequency baselines. Beating only uniform does not establish useful pattern learning. `goal_learning_status` also includes the CPU shadow state. A replay score is exploratory; prospective shadow results, paper net P&L and accepted Gemma updates remain different evidence. Losses do not become profitable labels.
21+
22+
## Internet and compute
23+
24+
Public sources, guest browser/shell, local deliverables, registered feeds, owned compute trials and eligible free-compute proposals are available. The master chooses operations through tools and sees their receipts. Existing network boundaries, service terms, credentials, eligibility and hardware limits still apply. Exact external submissions remain reviewable through the existing operator approval operation. No paid service, real trade, invented eligibility, human impersonation or bypass of service limits is enabled.
25+
26+
## Installation
27+
28+
`tools/update-synta-goal-work.sh` preserves the live profile, long-term goal, weights, ledgers, Windows policy and archived reports. It explicitly enables hypothetical capital research while keeping real spendable capital zero, spending disabled and real orders disabled. The old policy is archived. Startup verification waits up to 30 seconds for a running mission process; that is not proof of a completed training cycle.
29+
30+
Validation includes actual weight changes from failed forecasts, causal feature cutoffs, overlapping-horizon scoring, paired-label deduplication, altered-artifact rejection, extended tool sequences, beyond-seed execution, protected fallback and startup entering the learning loop. Live operator hardware and profitable strategies are not independently verified.

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

‎rlm/v100/background_observer.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ def tick(root: Path) -> None:
1919

2020
try:
2121
goal_tick(root)
22+
from rlm.v100.goal_policy import update
23+
24+
update(root)
2225
except (OSError, ValueError, RuntimeError, requests.RequestException) as error:
2326
ActivityLog(root, "controller", "goal-learning").write(
2427
"errors", "goal-observer-failed", {"detail": str(error)[:400]}

‎rlm/v100/drones.py‎

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
from contextlib import contextmanager
1313
from pathlib import Path
1414

15+
import requests
16+
1517
from rlm.v100.activity import ActivityLog
1618
from rlm.v100.common import atomic_json, load_profile
1719

@@ -164,13 +166,12 @@ def execute(root: Path, job: dict) -> dict:
164166
)
165167
from rlm.v100.competition import helper_client
166168
from rlm.v100.goals import load_goal
169+
from rlm.v100.income_work import accepted_master
167170
from rlm.v100.mission_chat import preferences
168171
from rlm.v100.remote_helper import remote_profile, selected_helper
169172
from rlm.v100.researchers import research_task
170173

171174
if preferences(root).get("remote_helper_enabled", True) is False:
172-
from rlm.v100.income_work import accepted_master
173-
174175
client = accepted_master(root, job["branch"])
175176
else:
176177
profile = load_profile(selected_helper(root), root)
@@ -180,12 +181,30 @@ def execute(root: Path, job: dict) -> dict:
180181
)
181182
profile["runtime"]["max_timeout"] = min(120, profile["runtime"]["max_timeout"])
182183
client = helper_client(profile, root, job["branch"])
183-
client.identity()
184+
try:
185+
client.identity()
186+
except requests.RequestException as error:
187+
ActivityLog(root, job["branch"], "resident-" + job["kind"]).write(
188+
"steps",
189+
"local-master-fallback",
190+
{
191+
"reason": str(error)[:300],
192+
"scope": "Remote transport unavailable; accepted local master only, no changed-identity fallback",
193+
},
194+
)
195+
client = accepted_master(root, job["branch"])
196+
client.research_tool_steps = 6
184197
client.activity_actor = "resident-" + job["kind"]
185198
client.research_tool_names = {
186199
"income_opportunities",
187200
"register_income_opportunity",
188201
"goal_learning_status",
202+
"suggest_goal_forecast",
203+
"sandbox_run",
204+
"sandbox_status",
205+
"propose_compute_trial",
206+
"compute_trial_status",
207+
"propose_colab_trial",
189208
"observe_goal_source",
190209
"predict_goal_pattern",
191210
"mission_evidence",

‎rlm/v100/goal_learning.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,8 +439,13 @@ def status(root: Path) -> dict:
439439
"goal_id": admission_record.get("goal_id"),
440440
"scope": "Not admission evidence for the active goal",
441441
}
442+
policy_path = root / "research/goal-policy/status.json"
443+
shadow = json.loads(policy_path.read_text()) if policy_path.exists() else None
444+
if shadow and shadow.get("goal_id") != (goal or {}).get("id"):
445+
shadow = None
442446
return {
443447
"goal_id": goal["id"] if goal else None,
448+
"cpu_shadow_policy": shadow,
444449
"resolved_total": sum(item["outcome"] is not None for item in active_items),
445450
"pending_total": sum(
446451
item["outcome"] is None and item["due"] + 300 >= time.time() for item in active_items

‎rlm/v100/goal_observer.py‎

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Forward-only financial task commissioning, independent of model tool choice.
22
3-
These are declared uniform-probability reference forecasts, never claimed model
4-
insight, trading signals, fees verification, or proof of profitable patterns.
3+
Uniform references and separately identified learned CPU shadow forecasts are
4+
never claimed Gemma insight, trading signals, fees verification or profit.
55
"""
66

77
import json
@@ -54,7 +54,7 @@ def tick(root: Path) -> dict:
5454
"SELECT data FROM forecasts WHERE outcome IS NULL AND due>=?", (now - 300,)
5555
)
5656
]
57-
if len(pending) >= 24:
57+
if len(pending) > 20:
5858
return {
5959
"state": "waiting",
6060
"goal_id": goal["id"],
@@ -65,6 +65,8 @@ def tick(root: Path) -> dict:
6565
for row in pending
6666
if row["plan"]["long"]["id"] == goal["id"]
6767
}
68+
from rlm.v100.goal_policy import suggest
69+
6870
results = []
6971
for offset in range(2):
7072
product = PRODUCTS[(index + offset) % len(PRODUCTS)]
@@ -91,12 +93,30 @@ def tick(root: Path) -> dict:
9193
"probabilities": [1 / 3, 1 / 3, 1 / 3],
9294
},
9395
)
96+
learned = suggest(root, observation["id"], 300, max(abs(value) * 0.001, 0.000001))
97+
shadow = predict(
98+
root,
99+
"B",
100+
{
101+
"question": f"Controller learned shadow: {product}-USD direction over 300 seconds",
102+
"rationale": "CPU online shadow policy trained on independently settled labels; prospective test, not Gemma skill or a trading instruction. Policy data SHA256: "
103+
+ learned["policy_data_sha256"],
104+
"evidence": [observation["id"]],
105+
"target": observation["id"],
106+
"horizon_seconds": 300,
107+
"threshold": max(abs(value) * 0.001, 0.000001),
108+
"probabilities": learned["probabilities"],
109+
},
110+
)
94111
results.append(
95112
{
96113
"product": product,
97114
"id": receipt["id"],
98115
"state": receipt["state"],
99116
"due": receipt["due"],
117+
"shadow_id": shadow["id"],
118+
"shadow_probabilities": learned["probabilities"],
119+
"shadow_trained_outcomes": learned["trained_outcomes"],
100120
}
101121
)
102122
except Exception as error:

‎rlm/v100/goal_policy.py‎

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
"""Online CPU forecast learner over independently settled, audited goal labels.
2+
3+
This shadow policy is separate from Gemma adapters and never places orders.
4+
Prequential scores use only outcomes available before each forecast commitment.
5+
"""
6+
7+
import json
8+
import math
9+
from pathlib import Path
10+
11+
from rlm.v100.common import atomic_json
12+
from rlm.v100.goal_learning import database, digest, observation, records
13+
from rlm.v100.goals import load_goal
14+
15+
FEATURES = ("bias", "last_change", "mean_change", "change_deviation", "history_count", "horizon")
16+
17+
18+
def vector(root: Path, target: dict, horizon: int, threshold: float) -> list[float]:
19+
if type(horizon) is not int or not 60 <= horizon <= 604800:
20+
raise ValueError("Use the goal forecast horizon budget")
21+
if type(threshold) not in (int, float) or not math.isfinite(threshold) or threshold <= 0:
22+
raise ValueError("Use a positive finite absolute threshold")
23+
with database(root) as db:
24+
# Evidence is host-observed and strictly no later than the target snapshot.
25+
rows = db.execute(
26+
"SELECT id, data FROM observations WHERE time<=? ORDER BY time DESC, id DESC",
27+
(target["acquired"],),
28+
)
29+
history = []
30+
for row in rows:
31+
candidate = json.loads(row["data"])
32+
if (candidate["url"], candidate["field"]) == (target["url"], target["field"]):
33+
history.append(observation(root, row["id"]))
34+
if len(history) == 8:
35+
break
36+
history.sort(key=lambda row: row["acquired"])
37+
changes = [
38+
(later["value"] - earlier["value"]) / threshold
39+
for earlier, later in zip(history, history[1:], strict=False)
40+
if later["acquired"] > earlier["acquired"]
41+
]
42+
mean = sum(changes) / len(changes) if changes else 0.0
43+
deviation = math.sqrt(sum((x - mean) ** 2 for x in changes) / len(changes)) if changes else 0.0
44+
values = [
45+
1.0,
46+
changes[-1] if changes else 0.0,
47+
mean,
48+
deviation,
49+
len(changes) / 7,
50+
math.log1p(horizon / 300),
51+
]
52+
if any(not math.isfinite(value) for value in values):
53+
raise ValueError("Nonfinite causal forecast feature")
54+
return [max(-10.0, min(10.0, value)) for value in values]
55+
56+
57+
def probabilities(weights: list[list[float]], features: list[float]) -> list[float]:
58+
scores = [sum(w * x for w, x in zip(row, features, strict=True)) for row in weights]
59+
peak = max(scores)
60+
exponents = [math.exp(score - peak) for score in scores]
61+
total = sum(exponents)
62+
return [value / total for value in exponents]
63+
64+
65+
def update_weights(weights: list[list[float]], features: list[float], actual: int) -> None:
66+
predicted = probabilities(weights, features)
67+
for label, row in enumerate(weights):
68+
for index, value in enumerate(features):
69+
row[index] = max(
70+
-5.0,
71+
min(
72+
5.0,
73+
row[index]
74+
+ 0.03 * ((label == actual) - predicted[label]) * value
75+
- 0.0003 * row[index],
76+
),
77+
)
78+
79+
80+
def update(root: Path) -> dict:
81+
verified = records(root)[-512:]
82+
goal_id = (load_goal(root) or {}).get("id")
83+
identity = digest({"goal": goal_id, "records": verified, "features": FEATURES, "version": 1})
84+
folder = root / "research/goal-policy"
85+
path = folder / "status.json"
86+
if path.exists():
87+
prior = json.loads(path.read_text())
88+
checksum = prior.pop("state_sha256")
89+
if digest(prior) != checksum:
90+
raise ValueError("Shadow policy artifact changed")
91+
if prior.get("data_sha256") == identity:
92+
return prior
93+
samples = []
94+
with database(root) as db:
95+
for row in verified:
96+
forecast = json.loads(
97+
db.execute(
98+
"SELECT data FROM forecasts WHERE id=?", (row["verification"]["forecast_id"],)
99+
).fetchone()[0]
100+
)
101+
spec = forecast["specification"]
102+
outcome = json.loads(
103+
db.execute(
104+
"SELECT outcome FROM forecasts WHERE id=?",
105+
(row["verification"]["forecast_id"],),
106+
).fetchone()[0]
107+
)
108+
target = observation(root, spec["target"])
109+
resolved = observation(root, outcome["observation"])
110+
samples.append(
111+
{
112+
"commitment": digest(
113+
[spec["target"], spec["horizon_seconds"], spec["threshold"]]
114+
),
115+
"created": forecast["created"],
116+
"resolved": resolved["acquired"],
117+
"features": vector(root, target, spec["horizon_seconds"], spec["threshold"]),
118+
"actual": ("down", "flat", "up").index(row["messages"][-1]["content"]),
119+
"id": row["verification"]["forecast_id"],
120+
}
121+
)
122+
# Reference and shadow forecasts on the same target are one training label,
123+
# not two independent observations. Retain the first available resolution.
124+
unique = {}
125+
for sample in sorted(samples, key=lambda row: row["resolved"]):
126+
unique.setdefault(sample["commitment"], sample)
127+
samples = list(unique.values())
128+
# Predict before applying labels that arrive later. Overlapping horizons cannot
129+
# leak their future labels into another earlier forecast's score.
130+
events = sorted(
131+
[(row["created"], 0, row["id"], row) for row in samples]
132+
+ [(row["resolved"], 1, row["id"], row) for row in samples],
133+
key=lambda event: event[:3],
134+
)
135+
weights = [[0.0] * len(FEATURES) for _ in range(3)]
136+
seen, scores, flat_scores, frequency_scores = 0, [], [], []
137+
counts = [0, 0, 0]
138+
for _, kind, _, row in events:
139+
if kind == 0:
140+
if seen >= 8:
141+
prediction = probabilities(weights, row["features"])
142+
scores.append(
143+
sum((p - (i == row["actual"])) ** 2 for i, p in enumerate(prediction))
144+
)
145+
flat_scores.append(0.0 if row["actual"] == 1 else 2.0)
146+
frequency = [(count + 1) / (seen + 3) for count in counts]
147+
frequency_scores.append(
148+
sum((p - (i == row["actual"])) ** 2 for i, p in enumerate(frequency))
149+
)
150+
else:
151+
update_weights(weights, row["features"], row["actual"])
152+
counts[row["actual"]] += 1
153+
seen += 1
154+
result = {
155+
"schema": "synta-goal-policy-v1",
156+
"goal_id": goal_id,
157+
"data_sha256": identity,
158+
"features": list(FEATURES),
159+
"weights": weights,
160+
"trained_outcomes": seen,
161+
"weights_changed": bool(seen),
162+
"prequential_samples": len(scores),
163+
"prequential_brier": sum(scores) / len(scores) if scores else None,
164+
"uniform_baseline_brier": 2 / 3,
165+
"prequential_flat_baseline_brier": sum(flat_scores) / len(flat_scores)
166+
if flat_scores
167+
else None,
168+
"prequential_frequency_baseline_brier": sum(frequency_scores) / len(frequency_scores)
169+
if frequency_scores
170+
else None,
171+
"state": "shadow learning" if seen else "waiting for verified outcomes",
172+
"scope": "Online supervised CPU shadow policy; losses and failed forecasts included. This is not Gemma weight training, a trading signal or verified income. Prequential replay is exploratory; new shadow forecasts are prospective tests.",
173+
}
174+
atomic_json(path, {**result, "state_sha256": digest(result)})
175+
return result
176+
177+
178+
def suggest(root: Path, target: str, horizon_seconds: int, threshold: float) -> dict:
179+
state = update(root)
180+
value = observation(root, target)
181+
if value["value"] is None:
182+
raise ValueError("Learned forecast needs a numeric target")
183+
return {
184+
"target": target,
185+
"probabilities": probabilities(
186+
state["weights"], vector(root, value, horizon_seconds, threshold)
187+
),
188+
"trained_outcomes": state["trained_outcomes"],
189+
"policy_data_sha256": state["data_sha256"],
190+
"prequential_brier": state["prequential_brier"],
191+
"scope": state["scope"],
192+
}

0 commit comments

Comments
 (0)