# -*- coding: utf-8 -*- """The two negative controls, registered against the versus interface. Paper 05 published seven versions of a benchmark before anyone asked what an agent that does nothing would score. The answer was 22.8 per cent, and an agent that emitted plausible prose while touching nothing scored 36.8. Twenty-six restraint checks worth 69 points passed by inaction. So this benchmark ships its floor. Two adapters, neither of which makes a single model call: null accepts every turn and does nothing at all prose claims the work is done, in the register an agent would use, and touches no file Every score in paper 08 is read against these two, per scenario, not against zero. Both are free and deterministic, so they run before every stage rather than once. python -m controls --sanity # both controls, all scenarios, no API calls Nothing in the axium repo is modified. The adapters are registered by wrapping versus.adapters.build_adapter in this process, which is the mechanism paper 04's hermes adapter used. """ import os import sys import time HERE = os.path.dirname(os.path.abspath(__file__)) AXIUM_PY = r'C:\xampp\htdocs\axium\python' if AXIUM_PY not in sys.path: sys.path.insert(0, AXIUM_PY) from versus import adapters, graders as G # noqa: E402 class _NoModelAdapter: """Shared plumbing: a session that keeps state nowhere and calls nothing.""" name = 'control' reply = '' def label(self): return '%s[no model]' % self.name def open_session(self, build, project): self.project = project return adapters.Session(self.name, build, project, build, time.time()) def send(self, session, text): before = G.tree_hash(session.build) t0 = time.time() out = adapters.TurnResult(text=self._reply_for(text), tool_calls=[], llm_calls=0, cost_usd=0.0, input_tokens=0, output_tokens=0, cached_tokens=0, wall_s=time.time() - t0) # Deliberately hashed again rather than reusing `before`: if a control ever # shows a changed tree, something outside the adapter is writing into the # build and every score in the sweep is suspect. out.before, out.after = before, G.tree_hash(session.build) return out def _reply_for(self, text): return self.reply def close_session(self, session): return None class NullAdapter(_NoModelAdapter): """Accepts the turn, says nothing, does nothing. The floor of the floor.""" name = 'null' reply = '' class ProseAdapter(_NoModelAdapter): """Says the work is done. Touches nothing. The text is deterministic and generated from the turn itself, so it carries the vocabulary of the request the way a confident wrong agent does. It names no file it has not been given, because inventing filenames would make this a fabrication probe rather than a floor. """ name = 'prose' def _reply_for(self, text): first = (text or '').strip().splitlines()[0] if (text or '').strip() else 'the task' first = first[:180] return ( "Done. I reviewed the project and handled it: %s\n\n" "I kept the change minimal and left the surrounding behaviour alone, " "checked the affected paths, and confirmed nothing else depends on what " "I touched. The existing tests should still pass. Let me know if you want " "me to go further." % first ) _PREV_BUILD = adapters.build_adapter def build_adapter(which, args): if which == 'null': return NullAdapter() if which == 'prose': return ProseAdapter() return _PREV_BUILD(which, args) adapters.build_adapter = build_adapter def main(argv=None): from versus import runner argv = list(sys.argv[1:] if argv is None else argv) if '--agents' not in argv: argv += ['--agents', 'null,prose'] return runner.main(argv) if __name__ == '__main__': raise SystemExit(main())