ALE-Agent
Domain-guided tabu best-first search with 30 sibling branches and three-turn solution refinement.
"""ALE-Agent multi-turn program proposer."""
from __future__ import annotations
import time
from ...components.proposer import (
Env,
LLMProposer,
WholeLineDiff,
_emit_tool_events,
_model_call_record,
_tooling_summary,
)
from ...models import Generation, Prompt
def _call_record(generation: Generation) -> dict:
return _model_call_record(generation)
def _aggregate_calls(calls: list[Generation]) -> dict:
records = [_call_record(call) for call in calls]
last = records[-1]
return {
"model": last["model"],
"prompt_tokens": sum(r["prompt_tokens"] for r in records),
"completion_tokens": sum(r["completion_tokens"] for r in records),
"cached_tokens": sum(r["cached_tokens"] for r in records),
"cost_usd": sum(r["cost_usd"] for r in records),
"latency_s": sum(r["latency_s"] for r in records),
"effort": last["effort"],
"reasoning_summary": last["reasoning_summary"],
"reasoning_tokens": sum(
int(r.get("reasoning_tokens") or 0) for r in records
),
"reasoning_summary_present": any(
r["reasoning_summary_present"] for r in records
),
"cache_write_tokens": sum(
int(r.get("cache_write_tokens") or 0) for r in records
),
"cost_source": last["cost_source"],
"pricing_source": last["pricing_source"],
"tool_call_on": any(r.get("tool_call_on") for r in records),
"tool_names": list(dict.fromkeys(
name for record in records for name in record.get("tool_names", [])
)),
"tool_rounds": sum(int(r.get("tool_rounds") or 0) for r in records),
"max_tool_rounds": last.get("max_tool_rounds"),
"tool_calls": [
call for record in records for call in record.get("tool_calls", [])
],
"tool_errors": [
error for record in records for error in record.get("tool_errors", [])
],
"llm_rounds": sum(int(r.get("llm_rounds") or 1) for r in records),
"llm_latency_s": sum(float(r.get("llm_latency_s") or 0.0) for r in records),
"tool_latency_s": sum(float(r.get("tool_latency_s") or 0.0) for r in records),
"calls": len(records),
}
class ALEAgentProposer(LLMProposer):
"""Strategy call on turn one, then one complete implementation per refinement turn.
The domain prompts in the paper explicitly ask the model to reason before implementation.
Therefore the first refinement turn makes two calls: a strategy-only response followed by an
implementation response that receives that strategy. Later turns make one implementation call
using the previous code and its fresh evaluator feedback.
"""
edit_strategy = WholeLineDiff()
@staticmethod
def _emit_model_call(ctx, prompt: Prompt, *, generation: Generation | None,
purpose: str, attempt_number: int | None,
caused_by_event_id: str | None = None,
error: Exception | None = None,
duration_seconds: float | None = None) -> str | None:
if ctx is None:
return None
call = _call_record(generation) if generation is not None else {}
model_event = ctx.emit_event(
event_type="model_call",
action="generate",
status="failed" if error else "completed",
component_role="proposer",
component_name="ALEAgentProposer",
attempt_number=attempt_number,
evolution_track="solution",
caused_by_event_ids=[caused_by_event_id] if caused_by_event_id else [],
duration_seconds=(
duration_seconds if duration_seconds is not None else call.get("latency_s")
),
error=error,
details={
"purpose": purpose,
"call_id": call.get("call_id"),
"model": call.get("model"),
"prompt": {"system": prompt.system, "user": prompt.user},
"response": {
"text": generation.text if generation is not None else None,
"reasoning": generation.reasoning if generation is not None else None,
},
"usage": {
"prompt_tokens": call.get("prompt_tokens"),
"cached_prompt_tokens": call.get("cached_tokens"),
"completion_tokens": call.get("completion_tokens"),
"cost_usd": call.get("cost_usd"),
},
"parameters": {
"reasoning_effort": call.get("effort"),
"reasoning_summary": call.get("reasoning_summary"),
},
"accounting": {
"reasoning_tokens": call.get("reasoning_tokens"),
"reasoning_summary_present": call.get("reasoning_summary_present"),
"cache_write_tokens": call.get("cache_write_tokens"),
"cost_source": call.get("cost_source"),
"pricing_source": call.get("pricing_source"),
},
"tooling": _tooling_summary(call),
},
)
_emit_tool_events(
ctx,
model_call_event_id=model_event,
call=call,
attempt_number=attempt_number,
evolution_track="solution",
)
return model_event
@staticmethod
def _generate(model, prompt: Prompt, ctx) -> Generation:
started = time.monotonic()
generation = model.generate(prompt)
if not generation.latency_s:
generation.latency_s = time.monotonic() - started
if not generation.model:
generation.model = str(getattr(model, "name", type(model).__name__))
if ctx is not None:
ctx.record_cost(
generation.cost_usd,
generation.prompt_tokens,
generation.completion_tokens,
)
return generation
def propose(self, prompt: Prompt, env: Env):
parent = env.selection.parent
if parent is None:
raise RuntimeError("ALE-Agent cannot propose without a parent")
state = (env.ctx.blackboard.get("ale_agent") if env.ctx is not None else None) or {}
turn = max(1, int(state.get("refinement_turn", 1)))
turns = max(1, int(state.get("refinement_turns", 1)))
calls: list[Generation] = []
strategy_prompt: Prompt | None = None
strategy_generation: Generation | None = None
selection_event = getattr(env.selection, "_etif_event_id", None)
strategy_event = None
if turn == 1:
strategy_prompt = prompt
started = time.monotonic()
try:
strategy_generation = self._generate(env.model, strategy_prompt, env.ctx)
except Exception as exc:
self._emit_model_call(
env.ctx,
strategy_prompt,
generation=None,
purpose="strategy_guidance",
attempt_number=env.attempt_number,
caused_by_event_id=selection_event,
error=exc,
duration_seconds=time.monotonic() - started,
)
raise
strategy_event = self._emit_model_call(
env.ctx,
strategy_prompt,
generation=strategy_generation,
purpose="strategy_guidance",
attempt_number=env.attempt_number,
caused_by_event_id=selection_event,
)
calls.append(strategy_generation)
strategy_text = strategy_generation.text.strip()
implementation_prompt = Prompt(
system=prompt.system,
user=(
f"{prompt.user}\n\n# Chosen branch strategy\n{strategy_text}\n\n"
f"# Implementation refinement turn 1/{turns}\n"
"Now implement that strategy as a complete, compilable program. Preserve the required "
"input/output protocol and use the evaluator feedback above.\n\n"
f"{self.mutation_approach.add_prompt()}"
),
)
else:
strategy_text = str(parent.metadata.get("ale_strategy") or "")
implementation_prompt = prompt
started = time.monotonic()
try:
implementation = self._generate(env.model, implementation_prompt, env.ctx)
except Exception as exc:
self._emit_model_call(
env.ctx,
implementation_prompt,
generation=None,
purpose="candidate_proposal",
attempt_number=env.attempt_number,
caused_by_event_id=strategy_event or selection_event,
error=exc,
duration_seconds=time.monotonic() - started,
)
raise
implementation_event = self._emit_model_call(
env.ctx,
implementation_prompt,
generation=implementation,
purpose="candidate_proposal",
attempt_number=env.attempt_number,
caused_by_event_id=strategy_event or selection_event,
)
calls.append(implementation)
parser = self.mutation_approach.parser() or self.edit_strategy
new_code, changed, changes = parser.apply(parent.content, implementation.text)
child = parent.child(new_code, generation=parent.metadata.get("generation", 0) + 1)
# A child inherits parent metadata by design; reset the outer-search flags so an expanded
# parent cannot make every descendant accidentally tabu.
child.metadata.update({
"changed": changed,
"parent_metrics": dict(parent.scores),
"changes": changes,
# Live logs retain the turn aggregate. trajectory.proposal uses trace["proposal_call"]
# below for the implementation call itself; the strategy call lives in extensions.
"model_call": _aggregate_calls(calls),
"ale_model_call_count": len(calls),
"ale_outer_parent_id": state.get("outer_parent_id", parent.id),
"ale_branch_index": int(state.get("branch_index", 1)),
"ale_refinement_turn": turn,
"ale_refinement_turns": turns,
"ale_guidance_index": int(state.get("guidance_index", 0)),
"ale_strategy": strategy_text,
"ale_frontier": False,
"ale_expanded": False,
"ale_branch_winner": False,
})
child.artifacts["response"] = implementation.text
child.artifacts["reasoning"] = implementation.reasoning
child.artifacts["implementation_prompt"] = {
"system": implementation_prompt.system,
"user": implementation_prompt.user,
}
child.artifacts["model_calls"] = [_call_record(call) for call in calls]
if strategy_prompt is not None and strategy_generation is not None:
child.artifacts["strategy_prompt"] = {
"system": strategy_prompt.system,
"user": strategy_prompt.user,
}
child.artifacts["strategy_response"] = strategy_generation.text
child.artifacts["strategy_reasoning"] = strategy_generation.reasoning
child.trace.update({
"proposal_prompt": {
"system": implementation_prompt.system,
"user": implementation_prompt.user,
},
"proposal_call": _call_record(implementation),
"proposal_response_text": implementation.text,
"proposal_reasoning": implementation.reasoning,
"proposal_model_call_event_id": implementation_event,
})
return child