Class: Ace::Assign::Molecules::ForkSessionLauncher

Inherits:
Object
  • Object
show all
Defined in:
lib/ace/assign/molecules/fork_session_launcher.rb

Overview

Launches a forked assignment-driving session via CLI LLM providers.

Constant Summary collapse

DEFAULT_PROVIDER =
"claude:sonnet"
DEFAULT_TIMEOUT =
1800
DEFAULT_LAUNCH_MODE =
"auto"
VALID_LAUNCH_MODES =
%w[auto headless tmux].freeze
TMUX_POLL_INTERVAL =
0.5

Instance Method Summary collapse

Constructor Details

#initialize(config: nil, query_interface: Ace::LLM::QueryInterface, tmux_runner: nil, interactive_builder: nil) ⇒ ForkSessionLauncher

Returns a new instance of ForkSessionLauncher.



19
20
21
22
23
24
# File 'lib/ace/assign/molecules/fork_session_launcher.rb', line 19

def initialize(config: nil, query_interface: Ace::LLM::QueryInterface, tmux_runner: nil, interactive_builder: nil)
  @config = config || Ace::Assign.config
  @query_interface = query_interface
  @tmux_runner = tmux_runner || TmuxForkRunner.new
  @interactive_builder = interactive_builder || Ace::LLM::Molecules::InteractiveCommandBuilder.new
end

Instance Method Details

#launch(assignment_id:, fork_root:, provider: nil, cli_args: nil, timeout: nil, cache_dir: nil, launch_mode: nil) ⇒ Hash

Launch forked subtree execution synchronously.

Parameters:

  • assignment_id (String)

    Assignment identifier

  • fork_root (String)

    Subtree root step number

  • provider (String, nil) (defaults to: nil)

    Optional provider override

  • cli_args (String, nil) (defaults to: nil)

    Optional provider CLI args

  • timeout (Integer, nil) (defaults to: nil)

    Optional timeout override (seconds)

  • cache_dir (String, nil) (defaults to: nil)

    Assignment cache directory for last-message capture

  • launch_mode (String, nil) (defaults to: nil)

    Launch mode override (auto|headless|tmux)

Returns:

  • (Hash)

    QueryInterface response



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/ace/assign/molecules/fork_session_launcher.rb', line 36

def launch(assignment_id:, fork_root:, provider: nil, cli_args: nil, timeout: nil, cache_dir: nil, launch_mode: nil)
  resolved_provider = provider || config.dig("execution", "provider") || DEFAULT_PROVIDER
  resolved_timeout = timeout || config.dig("execution", "timeout") || DEFAULT_TIMEOUT
  resolved_mode = resolve_launch_mode(launch_mode)

  if resolved_mode == "tmux"
    launch_tmux(
      assignment_id: assignment_id,
      fork_root: fork_root,
      provider: resolved_provider,
      cli_args: cli_args,
      timeout: resolved_timeout,
      cache_dir: cache_dir
    )
  else
    launch_provider_session(
      assignment_id: assignment_id,
      fork_root: fork_root,
      provider: resolved_provider,
      cli_args: cli_args,
      timeout: resolved_timeout,
      cache_dir: cache_dir
    )
  end
end

#launch_provider_session(assignment_id:, fork_root:, provider:, cli_args: nil, timeout: nil, cache_dir: nil, last_message_file: nil, session_meta_file: nil) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/ace/assign/molecules/fork_session_launcher.rb', line 62

def launch_provider_session(assignment_id:, fork_root:, provider:, cli_args: nil, timeout: nil, cache_dir: nil,
  last_message_file: nil, session_meta_file: nil)
  resolved_provider = provider || config.dig("execution", "provider") || DEFAULT_PROVIDER
  resolved_timeout = timeout || config.dig("execution", "timeout") || DEFAULT_TIMEOUT
  scoped_assignment = "#{assignment_id}@#{fork_root}"
  prompt = "/as-assign-drive #{scoped_assignment}"
  last_msg_file = last_message_file || build_last_message_file(cache_dir, fork_root)

  result = query_interface.query(
    resolved_provider,
    prompt,
    system: nil,
    cli_args: cli_args,
    timeout: resolved_timeout,
    fallback: false,
    last_message_file: last_msg_file
  )

  # Layer 1 write: capture last message for non-Codex providers (or when Codex didn't write).
  # Safety: `query` blocks until the subprocess exits, so by this point Layer 2 (Codex
  # --output-last-message) has already finished writing. No other writer exists at this point.
  if last_msg_file && result[:text] && !result[:text].strip.empty?
    existing = File.exist?(last_msg_file) ? File.read(last_msg_file).strip : ""
    File.write(last_msg_file, result[:text]) if existing.empty?
  end

  (last_msg_file, result, prompt: prompt, session_meta_file: session_meta_file)

  result
rescue Ace::LLM::Error => e
  raise Error, "Fork session execution failed via #{resolved_provider}: #{e.message}"
end