Use GPT-5.6 Luna for SCVs

c7175ed8a878 · AtlantisPleb · · parent f0c12794bf0e

Use GPT-5.6 Luna for SCVs

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • modified docs/operations/scv-staging-qualification.md
  • modified docs/runtime-configuration.md
  • modified docs/scv-planning.md
  • modified lib/mix/tasks/openagents.scv.opencode.ex
  • modified lib/openagents/scv/executor/open_code.ex
  • modified lib/openagents/scv/worker.ex
  • modified test/openagents/scv/open_code_executor_test.exs
  • modified test/openagents/scv/run_test.exs

Diff

8 files changed, +88 -9

docs/operations/scv-staging-qualification.md modified +6 -4

@@ -43,6 +43,7 @@ to Cloud Logging, writes one terminal result, and exits.

43 43
44 44
   - `SCV_OBJECTIVE` with a bounded read-only inspection request.
45 45
   - `SCV_MODEL` with the admitted model identifier.
46
   - `SCV_REASONING_EFFORT=low` for the production-code-capable SCV baseline.
46 47
   - `SCV_REPOSITORY_REVISION` with the exact committed Git SHA.
47 48
   - `SCV_TIMEOUT_MS` and `SCV_HEARTBEAT_INTERVAL_MS` with bounded values.
48 49
   - `SCV_DIAGNOSTIC_LOGS=false`.

@@ -64,10 +65,11 @@ The Cloud Logging stream must contain these records for one run ID:

64 65
- one `openagents.scv.worker.result.v1` terminal result.
65 66
66 67
Verify that the result reports the `opencode` driver, `opencode-core`
67
environment, `read_only` permission profile, exact source SHA, successful exit
68
status, bounded duration, resource measurements, event counts, token totals,
69
and no output truncation. Verify the deployed job uses the dedicated service
70
account and contains no prohibited environment or secret references.
68
environment, `openai/gpt-5.6-luna` model, `low` reasoning effort, `read_only`
69
permission profile, exact source SHA, successful exit status, bounded duration,
70
resource measurements, event counts, token totals, and no output truncation.
71
Verify the deployed job uses the dedicated service account and contains no
72
prohibited environment or secret references.
71 73
72 74
Treat a successful execution as an environment qualification receipt only. Do
73 75
not call it an isolated-staging pass or enable writes. Before an SCV can write,
docs/runtime-configuration.md modified +1

@@ -63,6 +63,7 @@ run with these settings:

63 63
| `SCV_ENVIRONMENT` | `opencode-core` |
64 64
| `SCV_PERMISSION_PROFILE` | `read_only` |
65 65
| `SCV_MODEL` | Admitted OpenCode model identifier |
66
| `SCV_REASONING_EFFORT` | `low` by default; `none` is also admitted |
66 67
| `SCV_REPOSITORY_REVISION` | Exact 40-character lowercase Git SHA baked into the image |
67 68
| `SCV_RUN_ID` | Optional externally assigned UUID; the worker generates one when omitted |
68 69
| `SCV_TIMEOUT_MS` | Wall-clock limit for the OpenCode process |
docs/scv-planning.md modified +30 -2

@@ -44,6 +44,18 @@ The recommended first autonomous milestone is staging-only deployment of a

44 44
narrow, low-risk change class. Production autonomy is a later admission, not a
45 45
configuration toggle hidden inside the first release.
46 46
47
### Model capability boundary
48
49
SCVs use `openai/gpt-5.6-luna` with `low` reasoning by default. You may select
50
`none` for latency-sensitive, measured workloads. Do not run implementation or
51
production-code tasks with GPT-5.4. GPT-5.6 is the minimum admitted model family
52
for code generation by an SCV.
53
54
Model capability does not grant infrastructure authority. A code-writing SCV
55
must still use an isolated workspace, repository-scoped principal, durable
56
effect records, required tests, and Forge promotion policy before its code can
57
reach staging or production.
58
47 59
## SCV runtime boundary
48 60
49 61
Define an SCV as the durable execution and supervision contract. Do not define

@@ -143,7 +155,22 @@ background install. A trusted local `config_seed` may copy only dependency and

143 155
lock files into the isolated directory; it never copies OpenCode configuration
144 156
or authentication state.
145 157
146
### Proven local run
158
### GPT-5.6 local qualification receipt
159
160
On 2026-08-20, installed OpenCode `1.18.5` ran a read-only SCV against OpenCode
161
commit `b155b15694dbcc6768f11d2f25cc2bdd1f738ab4` with
162
`openai/gpt-5.6-luna` and `low` reasoning. The run read `package.json` and
163
returned the package name and a one-sentence repository description without
164
changing the checkout.
165
166
SCV run `f4955a8b-d0a1-42ad-bc8b-acf4c6cb48b8` succeeded in 5,475
167
milliseconds. It emitted six normalized events, completed one `read` call,
168
used 6 input, 59 output, and 11 reasoning tokens, and reported no truncation.
169
The terminal receipt recorded the exact model and reasoning effort. This run
170
qualifies the local model-selection and event path. It does not authorize an SCV
171
to write to a shared repository or deploy code.
172
173
### Historical GPT-5.4 read-only run
147 174
148 175
On 2026-08-20, the adapter ran installed OpenCode `1.18.5` against the inspected
149 176
OpenCode `dev` commit `b155b15694dbcc6768f11d2f25cc2bdd1f738ab4` with model

@@ -190,7 +217,8 @@ OPENCODE_BIN=/absolute/path/to/opencode \

190 217
OPENCODE_CONFIG_SEED=/absolute/path/to/trusted/opencode-config \
191 218
mix openagents.scv.opencode \
192 219
  --repo /absolute/path/to/target \
193
  --model openai/gpt-5.4-mini \
220
  --model openai/gpt-5.6-luna \
221
  --reasoning-effort low \
194 222
  --timeout-seconds 180 \
195 223
  --diagnostic-logs \
196 224
  --prompt 'Inspect the requested files without changing them.' \
lib/mix/tasks/openagents.scv.opencode.ex modified +3 -1

@@ -23,6 +23,7 @@ defmodule Mix.Tasks.Openagents.Scv.Opencode do

23 23
    prompt: :string,
24 24
    prompt_file: :string,
25 25
    model: :string,
26
    reasoning_effort: :string,
26 27
    opencode: :string,
27 28
    config_seed: :string,
28 29
    output: :string,

@@ -60,7 +61,8 @@ defmodule Mix.Tasks.Openagents.Scv.Opencode do

60 61
        api_key: System.get_env("OPENAI_API_KEY"),
61 62
        executable: Keyword.get(options, :opencode, default_executable()),
62 63
        config_seed: config_seed(options),
63
        model: Keyword.get(options, :model, "openai/gpt-5.4-mini"),
64
        model: Keyword.get(options, :model, "openai/gpt-5.6-luna"),
65
        reasoning_effort: Keyword.get(options, :reasoning_effort, "low"),
64 66
        output_root: output_root,
65 67
        timeout_ms: timeout_ms,
66 68
        diagnostic_logs: Keyword.get(options, :diagnostic_logs, false),
lib/openagents/scv/executor/open_code.ex modified +16 -1

@@ -13,7 +13,9 @@ defmodule OpenAgents.SCV.Executor.OpenCode do

13 13
  alias OpenAgents.SCV.ResourceSampler
14 14
15 15
  @schema "openagents.scv.opencode.run.v1"
16
  @default_model "openai/gpt-5.4-mini"
16
  @default_model "openai/gpt-5.6-luna"
17
  @default_reasoning_effort "low"
18
  @reasoning_efforts ~w(none low)
17 19
  @default_timeout_ms 5 * 60 * 1_000
18 20
  @maximum_timeout_ms 60 * 60 * 1_000
19 21
  @default_maximum_output_bytes 16 * 1_024 * 1_024

@@ -30,6 +32,7 @@ defmodule OpenAgents.SCV.Executor.OpenCode do

30 32
         :ok <-
31 33
           emit_event(input, "run_preparing", %{
32 34
             model: input.model,
35
             reasoning_effort: input.reasoning_effort,
33 36
             permission_profile: Atom.to_string(input.permissions)
34 37
           }),
35 38
         {:ok, paths} <- prepare_paths(input.output_root, input.run_id, input.config_seed) do

@@ -43,6 +46,7 @@ defmodule OpenAgents.SCV.Executor.OpenCode do

43 46
44 47
  defp validate_input(repository, prompt, options) do
45 48
    model = Keyword.get(options, :model, @default_model)
49
    reasoning_effort = Keyword.get(options, :reasoning_effort, @default_reasoning_effort)
46 50
    timeout_ms = Keyword.get(options, :timeout_ms, @default_timeout_ms)
47 51
48 52
    maximum_output_bytes =

@@ -68,6 +72,7 @@ defmodule OpenAgents.SCV.Executor.OpenCode do

68 72
    with {:ok, repository} <- validate_directory(repository),
69 73
         :ok <- validate_prompt(prompt),
70 74
         :ok <- validate_model(model),
75
         :ok <- validate_reasoning_effort(reasoning_effort),
71 76
         :ok <- validate_integer(timeout_ms, 1, @maximum_timeout_ms, :timeout_invalid),
72 77
         :ok <-
73 78
           validate_integer(

@@ -101,6 +106,7 @@ defmodule OpenAgents.SCV.Executor.OpenCode do

101 106
         prompt: prompt,
102 107
         prompt_bytes: byte_size(prompt),
103 108
         model: model,
109
         reasoning_effort: reasoning_effort,
104 110
         timeout_ms: timeout_ms,
105 111
         maximum_output_bytes: maximum_output_bytes,
106 112
         sample_interval_ms: sample_interval_ms,

@@ -586,6 +592,7 @@ defmodule OpenAgents.SCV.Executor.OpenCode do

586 592
        adapter: "opencode",
587 593
        executable: input.executable,
588 594
        model: input.model,
595
        reasoning_effort: input.reasoning_effort,
589 596
        diagnostic_logs: input.diagnostic_logs,
590 597
        config_seeded: not is_nil(input.config_seed),
591 598
        permission_profile: Atom.to_string(input.permissions),

@@ -649,6 +656,8 @@ defmodule OpenAgents.SCV.Executor.OpenCode do

649 656
      "json",
650 657
      "--model",
651 658
      input.model,
659
      "--variant",
660
      input.reasoning_effort,
652 661
      "--dir",
653 662
      input.repository
654 663
    ]

@@ -805,6 +814,12 @@ defmodule OpenAgents.SCV.Executor.OpenCode do

805 814
806 815
  defp validate_model(_model), do: {:error, :model_invalid}
807 816
817
  defp validate_reasoning_effort(reasoning_effort)
818
       when reasoning_effort in @reasoning_efforts,
819
       do: :ok
820
821
  defp validate_reasoning_effort(_reasoning_effort), do: {:error, :reasoning_effort_invalid}
822
808 823
  defp validate_integer(value, minimum, maximum, _error)
809 824
       when is_integer(value) and value >= minimum and value <= maximum,
810 825
       do: :ok
lib/openagents/scv/worker.ex modified +13 -1

@@ -12,7 +12,8 @@ defmodule OpenAgents.SCV.Worker do

12 12
  alias OpenAgents.SCV.OpenCodeReport
13 13
  alias OpenAgents.SCV.Run
14 14
15
  @default_model "openai/gpt-5.4-mini"
15
  @default_model "openai/gpt-5.6-luna"
16
  @default_reasoning_effort "low"
16 17
  @default_timeout_ms 300_000
17 18
  @default_output_root "/workspace/runs"
18 19
  @maximum_report_chunk_bytes 3_072

@@ -64,6 +65,13 @@ defmodule OpenAgents.SCV.Worker do

64 65
           fetch_value(environment, "SCV_ENVIRONMENT", "opencode-core", ["opencode-core"]),
65 66
         {:ok, permission_profile} <-
66 67
           fetch_value(environment, "SCV_PERMISSION_PROFILE", "read_only", ["read_only"]),
68
         {:ok, reasoning_effort} <-
69
           fetch_value(
70
             environment,
71
             "SCV_REASONING_EFFORT",
72
             @default_reasoning_effort,
73
             ["none", "low"]
74
           ),
67 75
         {:ok, timeout_ms} <-
68 76
           fetch_integer(environment, "SCV_TIMEOUT_MS", @default_timeout_ms, 1..3_600_000),
69 77
         {:ok, heartbeat_interval_ms} <-

@@ -82,6 +90,7 @@ defmodule OpenAgents.SCV.Worker do

82 90
         environment: scv_environment,
83 91
         permission_profile: permission_profile,
84 92
         model: Map.get(environment, "SCV_MODEL", @default_model),
93
         reasoning_effort: reasoning_effort,
85 94
         output_root: Map.get(environment, "SCV_OUTPUT_ROOT", @default_output_root),
86 95
         executable: Map.get(environment, "OPENCODE_BIN"),
87 96
         config_seed: optional_value(environment, "OPENCODE_CONFIG_SEED"),

@@ -103,6 +112,7 @@ defmodule OpenAgents.SCV.Worker do

103 112
        executable: input.executable,
104 113
        config_seed: input.config_seed,
105 114
        model: input.model,
115
        reasoning_effort: input.reasoning_effort,
106 116
        output_root: input.output_root,
107 117
        timeout_ms: input.timeout_ms,
108 118
        heartbeat_interval_ms: input.heartbeat_interval_ms,

@@ -233,6 +243,8 @@ defmodule OpenAgents.SCV.Worker do

233 243
      status: result.status,
234 244
      driver: result.scv.driver,
235 245
      environment: result.scv.environment,
246
      model: result.runtime.model,
247
      reasoning_effort: result.runtime.reasoning_effort,
236 248
      repository_revision: result.repository.git_sha,
237 249
      duration_ms: result.duration_ms,
238 250
      event_count: result.events.event_count,
test/openagents/scv/open_code_executor_test.exs modified +10

@@ -47,6 +47,8 @@ defmodule OpenAgents.SCV.OpenCodeExecutorTest do

47 47
    assert result.status == "succeeded"
48 48
    assert result.exit_status == 0
49 49
    assert result.runtime.permission_profile == "read_only"
50
    assert result.runtime.model == "openai/test-model"
51
    assert result.runtime.reasoning_effort == "low"
50 52
    assert result.events.event_count == 4
51 53
    assert result.events.diagnostic_line_count == 0
52 54
    assert result.events.invalid_event_count == 0

@@ -158,6 +160,13 @@ defmodule OpenAgents.SCV.OpenCodeExecutorTest do

158 160
               "prompt",
159 161
               Keyword.put(shared_options(context), :api_key, nil)
160 162
             )
163
164
    assert {:error, :reasoning_effort_invalid} =
165
             OpenCode.run(
166
               context.repository,
167
               "prompt",
168
               Keyword.put(shared_options(context), :reasoning_effort, "medium")
169
             )
161 170
  end
162 171
163 172
  defp shared_options(context) do

@@ -182,6 +191,7 @@ defmodule OpenAgents.SCV.OpenCodeExecutorTest do

182 191
    prompt=$(cat)
183 192
    if [ -z "$prompt" ]; then exit 30; fi
184 193
    if [ "$1" != "run" ]; then exit 26; fi
194
    case " $* " in *" --variant low "*) :;; *) exit 32;; esac
185 195
    case " $* " in *" --auto "*) exit 27;; esac
186 196
    case " $* " in *" $prompt "*) exit 31;; esac
187 197
    if [ "${OPENCODE_CONFIG_DIR:-}" != "${XDG_CONFIG_HOME:-}/opencode" ]; then exit 28; fi
test/openagents/scv/run_test.exs modified +9

@@ -98,6 +98,7 @@ defmodule OpenAgents.SCV.RunTest do

98 98
      "SCV_ENVIRONMENT" => "opencode-core",
99 99
      "SCV_PERMISSION_PROFILE" => "read_only",
100 100
      "SCV_MODEL" => "openai/test-model",
101
      "SCV_REASONING_EFFORT" => "none",
101 102
      "SCV_OUTPUT_ROOT" => context.output,
102 103
      "OPENAI_API_KEY" => "fixture-secret-key"
103 104
    }

@@ -110,6 +111,7 @@ defmodule OpenAgents.SCV.RunTest do

110 111
111 112
    assert result.status == "succeeded"
112 113
    assert result.runtime.permission_profile == "read_only"
114
    assert result.runtime.reasoning_effort == "none"
113 115
    assert_receive {:worker_event, %{type: "process_started", driver: "opencode"}}
114 116
115 117
    assert {:error, {:environment_value_not_admitted, "SCV_PERMISSION_PROFILE"}} =

@@ -117,6 +119,9 @@ defmodule OpenAgents.SCV.RunTest do

117 119
118 120
    assert {:error, {:environment_missing, "OPENAI_API_KEY"}} =
119 121
             Worker.run(Map.delete(environment, "OPENAI_API_KEY"))
122
123
    assert {:error, {:environment_value_not_admitted, "SCV_REASONING_EFFORT"}} =
124
             Worker.run(Map.put(environment, "SCV_REASONING_EFFORT", "medium"))
120 125
  end
121 126
122 127
  test "emits bounded structured report chunks before the terminal result" do

@@ -129,6 +134,7 @@ defmodule OpenAgents.SCV.RunTest do

129 134
      duration_ms: 100,
130 135
      repository: %{git_sha: String.duplicate("a", 40)},
131 136
      scv: %{driver: "opencode", environment: "opencode-core"},
137
      runtime: %{model: "openai/gpt-5.6-luna", reasoning_effort: "low"},
132 138
      events: %{event_count: 2, tool_calls: %{}, usage: %{}},
133 139
      resources: %{},
134 140
      artifacts: %{events_digest: String.duplicate("b", 64)},

@@ -145,6 +151,8 @@ defmodule OpenAgents.SCV.RunTest do

145 151
    chunks = Enum.drop(events, -1)
146 152
147 153
    assert terminal.type == "worker_finished"
154
    assert terminal.model == "openai/gpt-5.6-luna"
155
    assert terminal.reasoning_effort == "low"
148 156
    assert terminal.report.digest == digest
149 157
    assert terminal.report.chunk_count == length(chunks)
150 158
    refute Map.has_key?(terminal.report, :text)

@@ -168,6 +176,7 @@ defmodule OpenAgents.SCV.RunTest do

168 176
    if [ "${OPENAI_API_KEY:-}" != "fixture-secret-key" ]; then exit 21; fi
169 177
    prompt=$(cat)
170 178
    if [ -z "$prompt" ]; then exit 22; fi
179
    case " $* " in *" --variant low "*|*" --variant none "*) :;; *) exit 23;; esac
171 180
    printf '%s\n' '{"type":"step_start","timestamp":1,"sessionID":"ses_scv","part":{"type":"step-start"}}'
172 181
    printf '%s\n' '{"type":"step_finish","timestamp":2,"sessionID":"ses_scv","part":{"type":"step-finish","cost":0.001,"tokens":{"input":4,"output":2,"reasoning":1,"cache":{"read":0,"write":0}}}}'
173 182
    """

This page updates live while a promote is in flight · changelog