Reconstruct: a hidden program from input/output behaviour
reconstruct is a built-in workflow for black-box program reconstruction: a
hidden program can only be queried with inputs, and the agent must write a
program that behaves the same way. The workflow implements the active-exploration
protocol of the FromIO paper (arXiv 2605.15334,
Section 4.6) and adds optional verification disciplines borrowed from
rea.
- The workflow obtains
initial_batchobserved examples (default 2). - Each iteration starts one coding-agent session with every observed example
and the previous feedback; the agent writes
candidate.pyin the workspace. - The workflow runs the candidate on every observed example. A full pass is a promotion; a failing candidate gets the failures as feedback and no new examples.
- After a promotion the workflow asks the agent to propose the next batch of
inputs (
batch_stepmore than the previous batch), validates the proposal (exact count, no repeats, accepted by the oracle; a rejected proposal is retried up topropose_attemptstimes) and queries the hidden program. early_stopconsecutive promotions,max_iterationsiterations, a failed proposal or an exhausted token budget end the loop. The budget check runs after the current candidate has been evaluated and archived, so the last program the agent wrote is never lost.- Every iteration's candidate is archived in
<workspace>/.oc_candidates/; the finalcandidate.pyis the program archived by this run with the best stage score on the final example set (accuracy minus the paper's complexity and hard-coding penalties). Files left in that directory by an earlier run are ignored.
The workflow queries the hidden program; the agent never does. One coding session replaces one DIO-Agent evolution iteration of the paper.
| argument | default | rule |
|---|---|---|
random_share |
0.0 |
this fraction of every batch comes from ReconstructionTask.random_inputs (a source independent of the agent, such as the benchmark's input generator) instead of the agent |
coverage_gate |
false |
a promotion counts towards early stop only once the observed inputs cover the structural case categories for the task's input kind (empty, short, long, zero, negative, repeated ...); missing categories are requested from the agent; after three batches on the same missing set they stop blocking |
memorisation_gate |
false |
a candidate that embeds three or more observed inputs verbatim is not a verified pass: it is asked to rewrite on the same evidence, twice, before a batch is granted anyway |
complexity_gate |
false |
a pass by a candidate whose complexity penalty is at least 0.8 is granted its batch but not counted as a promotion, up to three times |
domain_check |
false |
proposed inputs with numbers outside [lo, hi] are rejected and the range is stated in the prompt |
On IO2CodeBench with gpt-6-luna, the plain protocol solved 104 of 115 tasks and
random_share=0.5 with coverage_gate solved 113; the other three disciplines
fixed two tasks and lost two others at a much higher token cost. See the
IO2Code example.
Binding a hidden program
The binding is a ReconstructionTask:
from opencollab.builtin_workflows import ReconstructionTask
task = ReconstructionTask(
input_type="list of integers",
input_constraints="Valid input is a list of integers.",
function_name="solve", # candidate.py must define solve(input_data)
workspace=workspace, # the agent's workspace (candidate.py lives here)
oracle=hidden_program, # input -> output; raise for an invalid input
evaluate=run_candidate, # async (program_path, inputs) -> [{"ok", "output"|"error"}]
tools=[*builtin_tools("bash", "file_read", "file_write", "apply_patch"), check_candidate],
random_inputs=generator, # (k, exclude) -> fresh inputs; needed for random_share
on_example=record, # (input, output, source) for the caller's own log
kind="list", lo=-1000, hi=1000, # input kind and numeric range for the gates
)evaluate should run the candidate where the agent's own code runs (for
example inside the agent's Docker container), never in the harness process.
kind is one of list, two_lists, int, pair, string, points or
other; other disables the coverage gate and the domain check.
Workflow arguments are recorded with the run's artifacts, so they must be
JSON-serializable: args["task"] is a handle to the binding, not the object.
register_task keeps the binding in this process and returns a token that the
workflow consumes:
from opencollab import OpenCollab
from opencollab.builtin_workflows import reconstruct, register_task
result = await OpenCollab(workspace, environment=container).workflow(
reconstruct,
{"task": register_task(task), "random_share": 0.5, "coverage_gate": True},
budget=5_000_000, max_steps=100, artifacts=run_dir / "artifacts",
)args["task"] may name a factory as module:callable; it is called with
args["task_args"] and must return a ReconstructionTask:
uv run opencollab workflow run reconstruct \
--args '{"task": "my_bench.binding:make_task", "task_args": {"name": "Abs_Current"}, "random_share": 0.5}'| field | meaning |
|---|---|
stop_reason |
early_stop, max_iterations, proposer_failed or budget_exhausted |
iterations, promotions |
coding sessions run, full passes among them |
known_examples, random_inputs |
observed examples, how many came from random_inputs |
examples |
every observed [input, output, source] |
events |
per-iteration record: known, passed, score, promoted, uncovered categories, memorised inputs, complexity |
final_program, final_score |
the archived candidate copied to candidate.py and its stage score |
coverage_uncovered_at_end |
categories still missing when the loop ended |
Scoring against held-out tests is the caller's job; the workflow never sees them.