Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
8380ceb
fix(session): head-truncation fallback for unrecoverable context over…
anandgupta42 Aug 27, 2026
361f7c9
feat(harness): proactive overflow estimation + agent finish protocol
anandgupta42 Aug 27, 2026
3a5a09b
fix(session): turn-boundary truncation + accurate tail token estimation
anandgupta42 Aug 27, 2026
b6f478b
feat(harness): Wave 1 structural fixes — summarizer integrity, trunca…
anandgupta42 Aug 27, 2026
0bfa719
feat(harness): Wave 2 core-loop fixes — termination path, task pinnin…
anandgupta42 Aug 27, 2026
d48d674
feat(harness): Wave 2 config schema — starvation/idle-done/pin knobs
anandgupta42 Aug 27, 2026
20c1661
feat(harness): Wave 3 reliability — context estimator safety margin, …
anandgupta42 Aug 27, 2026
3e1e988
test(harness): pin compaction.test.ts isOverflow suite to safety frac…
anandgupta42 Aug 27, 2026
b510f46
fix(harness): review-driven hardening — termination false-positives, …
anandgupta42 Aug 27, 2026
77abbf0
chore: neutralize internal fixture identifiers and planning shorthand…
anandgupta42 Aug 28, 2026
51feb09
fix(harness): track code-fence marker char+length in completion detec…
anandgupta42 Aug 28, 2026
3d01d49
fix(harness): compaction breaker returns stop and clears attempts; pr…
anandgupta42 Aug 28, 2026
e7b3a48
fix(harness): terminal finish outcomes take precedence over compaction
anandgupta42 Aug 28, 2026
5780502
fix(harness): head-truncation fallback cuts only at user boundaries, …
anandgupta42 Aug 28, 2026
706084f
fix(harness): run exit-code stickiness — spurious beforeExit can no l…
anandgupta42 Aug 28, 2026
f3d35e6
fix(harness): estimator safety fraction applies only to estimated tok…
anandgupta42 Aug 28, 2026
ca1f42d
fix(harness): clamp retained tail+ledger below the overflow trigger o…
anandgupta42 Aug 28, 2026
cdfb9da
fix(harness): carry the verbatim-tail turn count through the v2 confi…
anandgupta42 Aug 28, 2026
6be3d0d
fix(harness): mutation credit on success only, doom-loop stop latched…
anandgupta42 Aug 28, 2026
aef20e7
fix(harness): prototype-safe tool-call id tables (Map) + per-processo…
anandgupta42 Aug 28, 2026
f333429
fix(harness): scope the idle-done challenge attribution and abort for…
anandgupta42 Aug 28, 2026
78ead32
fix(harness): session-state eviction is LRU (refresh on access), not …
anandgupta42 Aug 28, 2026
0134e90
fix(harness): batch of small hardening fixes — conservative cap defau…
anandgupta42 Aug 28, 2026
8183789
chore: neutralize remaining planning shorthand in comments and test n…
anandgupta42 Aug 28, 2026
11b5224
chore: keep the relocated compaction check inside change markers
anandgupta42 Aug 28, 2026
c49df38
fix(harness): address external review feedback — small correctness an…
anandgupta42 Aug 28, 2026
e9bde73
fix(harness): second-wave review fixes — completion detector and run …
anandgupta42 Aug 28, 2026
c9f78ec
fix(harness): extend change markers over the braced tool-input-start …
anandgupta42 Aug 29, 2026
2a8850c
fix(harness): close the unpaired change marker in run accounting
anandgupta42 Aug 29, 2026
8f765a0
fix(harness): address multi-model review consensus — summarizer starv…
anandgupta42 Aug 29, 2026
3137696
fix(harness): fourth-pass review fixes — fence conformance, config bo…
anandgupta42 Aug 29, 2026
b9c5dca
fix(harness): fifth-pass review fixes — truncation fallbacks, ledger …
anandgupta42 Aug 29, 2026
3e50ebb
fix(harness): include the tool outcome in the doom-loop repeat signature
anandgupta42 Aug 30, 2026
a6b6c6d
chore(harness): wrap the dispatch-capped tool-error assignment in alt…
anandgupta42 Aug 30, 2026
54e93b7
fix(harness): sixth-pass — correct four defects the bots found in the…
anandgupta42 Aug 30, 2026
d55dc8a
Merge remote-tracking branch 'origin/main' into codex/harness-reliabi…
anandgupta42 Aug 30, 2026
e9ecdbd
fix(harness): close release-blocking review findings
anandgupta42 Aug 30, 2026
5f33361
Merge remote-tracking branch 'origin/main' into codex/harness-reliabi…
anandgupta42 Aug 30, 2026
66a2830
fix(harness): harden recovery and replay edge cases
anandgupta42 Aug 30, 2026
ef9cc3e
chore(harness): mark nudge generation wiring
anandgupta42 Aug 30, 2026
cddf8c2
fix(harness): close termination safety gaps
anandgupta42 Aug 30, 2026
420f438
fix(harness): close compaction cleanup review
anandgupta42 Aug 30, 2026
e7151f2
fix(harness): fail closed on mixed git commands
anandgupta42 Aug 30, 2026
13dfa1d
test(harness): accept async prompt cleanup finalizer
anandgupta42 Aug 30, 2026
22ad2f0
fix: close harness lifecycle review findings
anandgupta42 Aug 30, 2026
a95f5e5
fix: close remaining harness lifecycle findings
anandgupta42 Aug 30, 2026
0011ec3
fix: close final harness release findings
anandgupta42 Aug 30, 2026
1999fe3
fix: mark remaining harness release changes
anandgupta42 Aug 30, 2026
2592608
fix: mark harness execution cleanup
anandgupta42 Aug 30, 2026
bd03a23
merge: origin/main into feat/harness-reliability
anandgupta42 Aug 31, 2026
3982f0d
fix(harness): close four review findings — redaction, mutation gate, …
anandgupta42 Aug 31, 2026
4bbb259
fix(harness): a space-separated substitution after a configured verif…
anandgupta42 Aug 31, 2026
7f5385a
test: use Array.from instead of spread in the surrogate check
anandgupta42 Aug 31, 2026
69374ef
fix(harness): exempt UID:GID explicitly, not by absence of letters
anandgupta42 Aug 31, 2026
22f405d
fix(harness): redact observation masks before they are replayed
anandgupta42 Aug 31, 2026
49e95ef
fix(harness): scope the DONE completion instruction to run mode
anandgupta42 Aug 31, 2026
8b4dab7
refactor: convert the five new modules to the prescribed ESM shape
anandgupta42 Aug 31, 2026
ee7cb0e
fix(harness): close final reliability review gaps
anandgupta42 Aug 31, 2026
c27b880
chore(harness): protect final review fixes with markers
anandgupta42 Aug 31, 2026
a1bb0a6
fix(harness): close final context safety gaps
anandgupta42 Aug 31, 2026
dda3a2d
fix: resolve remaining harness review findings
anandgupta42 Aug 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions .github/meta/harness-review-followups.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Harness reliability review — deferred follow-ups

Deferred MED findings from the pre-PR release review (pre-PR adversarial review).
All 5 HIGH findings plus selected MED/LOW items were fixed on this branch; the
items below were explicitly deferred and are listed verbatim from the review.

[MED] packages/opencode/src/tool/truncation.ts:66 — the plain-async truncation path hardcodes 2,000 lines/50KiB while the Effect wrapper honors `tool_output` configuration — MCP output through `prompt.ts` therefore ignores user caps despite the shared-core claim — consolidate the wrappers or pass the resolved configuration through both, with parity tests.

[MED] packages/opencode/src/session/compaction.ts:663 (renderCarryAnchors) — carry-anchor trimming stops when one item remains — one oversized model-generated "Accomplished" item defeats `maxTokens` and can undo compaction — permit dropping or truncating the final item and assert the rendered result satisfies the cap.

[MED] packages/opencode/src/cli/cmd/idle-done.ts:157 — every command not recognized as read-only is treated as verification — an exit-zero install, cleanup, deployment, or arbitrary unknown command can satisfy the "green verify" precondition and trigger a false completion challenge — require configured or positively classified verification evidence; unknown commands should be ineligible.

[MED] packages/opencode/src/session/compaction.ts:70 — observation masks retain the first 80 characters of pruned output, while the ledger retains raw command/path/pattern text — credentials, authorization headers, query data, and signed URLs can survive pruning and be recopied into later synthetic prompts — retain only allowlisted metadata or hashes and apply shared secret redaction.

[MED] packages/opencode/src/session/compaction.ts:572 (renderLedger) — ledger capping repeatedly joins and re-estimates the whole array while removing one line at a time, after collecting the full session history — this is quadratic in unique writes and adds latency at the critical compaction path — bound collection early and trim using accumulated token costs or a single cutoff search.

The following items from a later review pass were also considered and deliberately
deferred (no behavior change on this branch):

[LOW] packages/opencode/src/cli/cmd/run/run-mode.ts — the run entrypoint writes its process-scoped mode marker into the environment for the process lifetime, and child processes inherit it; a nested interactive server launched from such a session would arm run-mode mechanisms for genuinely interactive clients — clear or scope the marker in the interactive entrypoints (mirroring the existing child-env cleanup for the sibling non-interactive marker).

[LOW] packages/opencode/src/session/compaction.ts — module-level per-session pin state and per-session tracker read maps grow without bound in a long-lived server process (the pin map has no production eviction path; per-session read tracking keeps one entry per unique path for the session's lifetime) — bound both with the same LRU pattern now used by the session-state stores.

[LOW] packages/opencode/src/session/prompt.ts — the post-compaction pinned-task reminder re-derives its source by streaming the FULL session history from the database on every generation once a session has compacted — cache the resolved pin source per session or query only the needed boundary messages.

[LOW] packages/opencode/src/session/termination.ts — the post-compaction three-option completion nudge (including the completion-token instruction) is injected in ALL modes; interactive users can see an occasional bare completion token line with no interactive function — mode-gate the nudge text or document the cosmetic change.

The following items came from an external multi-reviewer pass over this branch and
were deliberately deferred (no behavior change on this branch):

[MED] packages/opencode/src/cli/cmd/run.ts — a prompt retry after a client timeout or connection reset can hit the server AFTER it accepted the original POST, sending the same task again and creating a second user message / duplicate execution — the retry loop has no way to tell "not yet accepted" from "accepted, response lost." Needs a stable idempotency/message key the server honors, or a way to confirm the first attempt was never accepted before retrying.

[MED] packages/opencode/src/session/compaction.ts (fitHead) — the summarization-request budget reserves a fixed 2,000 tokens for the summary prompt, but the actual assembled prompt (default template + carry anchors + pin-summary addition + first-person reframe, or a plugin-supplied override) can exceed that on an active session — size the reservation from the actual assembled prompt instead of a constant.

[MED] packages/opencode/src/session/compaction.ts (buildLedger) — on a session's second or later auto-compaction, the ledger is built from the already-filtered/compacted message view, not the full session stream, so verified-write facts from before the first compaction silently drop out of later ledgers — build from the full stream and let selection filter afterward.

[LOW] packages/opencode/src/session/prompt.ts (uncountedTail) — the proactive overflow estimate sums tool-result tokens on messages AFTER the last-finished assistant message, but a tool call and its result can live on that SAME message when the turn is still mid-flight — those results are excluded from the estimate, so compaction can fire a step later than it should on a heavy tool-output turn.

[LOW] packages/opencode/src/tool/truncate-core.ts (preview, middle direction) — a degenerate `maxBytes: 1` config (no realistic caller sets this) can still allocate one byte to each of the head/tail halves and exceed the byte budget by a small margin; the equivalent `maxLines: 1` case was already fixed by degrading to tail-only — extend the same degrade to the byte-only case.

[LOW] packages/opencode/src/session/{termination,nudge,tool-result-cap}.ts, packages/opencode/src/cli/cmd/{run-accounting,idle-done}.ts — these 5 new modules use `export namespace` for organization, which the repo's module-shape convention (`packages/opencode/AGENTS.md`) asks new code to avoid in favor of flat exports + a bottom-of-file self-reexport. ~69 pre-existing files in the package already use the same pattern, so this is consistent with existing debt rather than a regression; fold into a holistic namespace-to-flat-exports cleanup across the package rather than converting these 5 files in isolation.

[LOW] packages/opencode/test/session/starvation.test.ts — the run-mode "armed" gate test re-implements the gate expression as a local helper instead of importing the real predicate from processor.ts, so a future change to the actual gate (added condition, reordered precedence, renamed exemption) would not be caught by this test — extract the gate into a shared, directly-testable predicate.

The following items came from a further multi-reviewer pass over this branch and
were deliberately deferred (no behavior change on this branch):

[MED] packages/opencode/src/session/llm.ts (addHistoricalToolStubs) — the stub-injection skip was narrowed to `toolChoice === "none" && no tools`, which closes the normal-turn regression, but the compaction summarizer still takes that path with a head that references historical tool calls. The skip rests on the claim that omitting both `tools` and `tool_choice` is universally accepted; the issue the stubs exist to fix concerned validation of referenced historical tool calls, which is orthogonal. Verify against the provider that originally needed the stubs before changing anything — reintroducing stubs here would advertise callable tools on a text-only call, so this is a provider-compatibility question, not a code cleanup.

[MED] packages/opencode/src/session/starvation.ts (repeatSignature / consecutive-signature counter) — the repeat signature folds the failure message in but the counter accumulates for successful calls too, so three identical SUCCESSFUL calls (re-running the same verification command, polling a status probe) reach the threshold and produce a directive asserting that repeating the call cannot change the result, which is untrue for a polling or verification call. Restricting accumulation to calls that carry a failure message narrows the detector's semantics and its overlap with the identical-args doom-loop ladder; make that change with validation data rather than in review, and note the directive text is prompt-visible.

[LOW] packages/opencode/src/cli/cmd/run-accounting.ts (termination) — `why_model_stopped` falls back to "stop" when no step-finish reason was ever recorded (an aborted run that never completed a step), so the run record attributes an ordinary stop to a session that never reported one. Distinguishing it needs a new value in the published record's enum, which is an output-contract change for downstream consumers — batch it with the next deliberate revision of the run record schema.

[LOW] packages/opencode/src/session/prompt.ts (post-compaction pin reminder) and packages/opencode/test/cli/idle-done.test.ts — the idle-done unit tests now pin production event ordering (step-finish part before the step's snapshot patch part) in two dedicated cases, but the shared fixtures still emit the patch first; converge the remaining fixtures on production ordering when the file is next touched.

The following items came from the fourth review pass over this branch and were
deliberately deferred (no behavior change on this branch):

[MED] packages/opencode/src/session/processor.ts (dispatch tool-result cap) — the per-result hard cap is applied on the successful `tool-result` branch only. A `tool-error` still persists an unbounded error string, and an interrupted running tool keeps partial output in metadata that `message-v2.ts` later replays as a tool result, so a failed call with very large stderr can still overflow the next request. Applying the cap to error text and interrupted partial output changes what gets PERSISTED on the failure path, which is where diagnostics come from — size it against real failure payloads before truncating them.

[MED] packages/opencode/src/cli/cmd/idle-done.ts (verify classification) — with no configured verify command, ANY non-read-only bash command is treated as a verification, so a mutating command that is not on the mutating-heads list (a build script that also writes generated sources, say) can register as the green verify it is not. The mutation watermark now advances for the in-place, redirection, and known-mutating-head forms, but the underlying "not read-only implies verification" inference still needs replacing with an explicit verify classifier.

[LOW] packages/opencode/src/cli/cmd/run.ts (--max-turns) — `--max-turns 0` disables the limit entirely because the guard is a truthiness check, and negative or fractional values are accepted without validation. For a governance control an explicit zero should not mean unlimited; fix it together with the CLI validation pass that gives the other numeric options explicit range errors, so the behavior change is documented in one place.

[LOW] packages/opencode/src/tool/bash.ts (child env) — `ALTIMATE_RUN_RESUMED` joins `ALTIMATE_RUN_MODE` as a marker that nested processes inherit from the bash tool's merged env. Its leak direction is benign (a nested run would use interactive pin selection), but it belongs in the same holistic decision about which run-scoped markers get stripped for child processes.
23 changes: 23 additions & 0 deletions packages/core/src/config/compaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,34 @@ import { NonNegativeInt } from "../schema"

export class Keep extends Schema.Class<Keep>("ConfigV2.Compaction.Keep")({
tokens: NonNegativeInt.pipe(Schema.optional),
// altimate_change start — V2 parity for the fork's verbatim-tail turn count
// (V1 compaction.tail_turns; 0 disables the tail entirely).
turns: NonNegativeInt.pipe(Schema.optional),
Comment thread
anandgupta42 marked this conversation as resolved.
// altimate_change end
}) {}

export class Info extends Schema.Class<Info>("ConfigV2.Compaction")({
auto: Schema.Boolean.pipe(Schema.optional),
prune: Schema.Boolean.pipe(Schema.optional),
keep: Keep.pipe(Schema.optional),
buffer: NonNegativeInt.pipe(Schema.optional),
// altimate_change start — V2 parity for the fork compaction keys (estimator
// safety margin, state ledger/summary carry, task pin). Same names as V1 so
// ConfigMigrateV1 can carry them through without renames.
// Reject NaN and infinities at document decode; finite out-of-range values
// are clamped at the one runtime boundary
// (SessionCompaction.contextSafetyFraction).
context_safety_fraction: Schema.Number.check(Schema.isFinite()).pipe(Schema.optional),
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
state_ledger: Schema.Boolean.pipe(Schema.optional),
ledger_max_tokens: NonNegativeInt.pipe(Schema.optional),
ledger_recent_calls: NonNegativeInt.pipe(Schema.optional),
summary_carry: Schema.Boolean.pipe(Schema.optional),
summary_first_person: Schema.Boolean.pipe(Schema.optional),
pin_task: Schema.Boolean.pipe(Schema.optional),
pin_max_tokens: NonNegativeInt.pipe(Schema.optional),
pin_window_fraction: Schema.Number.check(Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(1)).pipe(
Schema.optional,
),
pin_card_max_tokens: NonNegativeInt.pipe(Schema.optional),
// altimate_change end
}) {}
21 changes: 21 additions & 0 deletions packages/core/src/config/experimental.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ export * as ConfigExperimental from "./experimental"
import { Schema } from "effect"
import { Catalog } from "../catalog"
import { Policy as PolicyV2 } from "../policy"
// altimate_change start — V2 parity for the write-starvation breaker
import { PositiveInt } from "../schema"
// altimate_change end

// Each core domain exports the policy actions it supports. Adding an action to
// this union makes it valid in authored config while keeping Policy generic.
Expand All @@ -13,6 +16,24 @@ export class Policy extends Schema.Class<Policy>("ConfigV2.Experimental.Policy")
action: PolicyAction,
}) {}

// altimate_change start — V2 parity for the write-starvation breaker keys.
// Same names as V1 (config.ts experimental.starvation_breaker) so
// ConfigMigrateV1 can carry them through without renames.
export class StarvationBreaker extends Schema.Class<StarvationBreaker>("ConfigV2.Experimental.StarvationBreaker")({
mode: Schema.Literals(["off", "annotate", "armed"]).pipe(Schema.optional),
max_turns_without_mutation: PositiveInt.pipe(Schema.optional),
repeat_signature_threshold: PositiveInt.pipe(Schema.optional),
doom_loop_threshold: PositiveInt.pipe(Schema.optional),
polling_threshold_multiplier: PositiveInt.pipe(Schema.optional),
polling_pattern: Schema.String.pipe(Schema.optional),
exempt_agents: Schema.String.pipe(Schema.Array, Schema.optional),
generated_path_patterns: Schema.String.pipe(Schema.Array, Schema.optional),
}) {}
// altimate_change end

export class Experimental extends Schema.Class<Experimental>("ConfigV2.Experimental")({
policies: Policy.pipe(Schema.Array, Schema.optional),
// altimate_change start — V2 parity for the write-starvation breaker
starvation_breaker: StarvationBreaker.pipe(Schema.optional),
// altimate_change end
}) {}
3 changes: 3 additions & 0 deletions packages/core/src/config/tool-output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@ import { PositiveInt } from "../schema"
export class Info extends Schema.Class<Info>("ConfigV2.ToolOutput")({
max_lines: PositiveInt.pipe(Schema.optional),
max_bytes: PositiveInt.pipe(Schema.optional),
// altimate_change start — V2 parity for the per-tool-result dispatch cap
dispatch_max_tokens: PositiveInt.pipe(Schema.optional),
// altimate_change end
}) {}
12 changes: 12 additions & 0 deletions packages/core/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ export const PositiveInt = Schema.Int.check(Schema.isGreaterThan(0))
*/
export const NonNegativeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))

// altimate_change start — float32-exact lower bound for the compaction
// safety fraction.
//
// `Schema.toArbitrary`'s fast-check generator requires `.check()` bounds to be
// exact 32-bit floats, and 0.1 is not float32-representable. Rounding UP to the
// nearest float32 (`Math.fround(0.1)` ≈ 0.10000000149) makes the schema reject
// the documented minimum `0.1`, so the bound has to round DOWN instead. This is
// the nearest float32 strictly below 0.1 (~1.3e-8 under), which satisfies the
// generator and still accepts the advertised lower endpoint.
export const SAFETY_FRACTION_MIN = Math.fround(0.1 - 1e-8)
// altimate_change end

/**
* Relative file path (e.g., `src/components/Button.tsx`).
*/
Expand Down
Loading
Loading