Skip to content

Non-orch surface: thrash pause, resume, feedback, telemetry names - #443

Merged
TheGreatAxios merged 18 commits into
mainfrom
wave/non-orch-surface-r1
Aug 9, 2026
Merged

Non-orch surface: thrash pause, resume, feedback, telemetry names#443
TheGreatAxios merged 18 commits into
mainfrom
wave/non-orch-surface-r1

Conversation

@TheGreatAxios

Copy link
Copy Markdown
Collaborator

Summary

Wave of non-orchestration surface work on Corbits Code:

  • CL-5611 — tool-only thrash pause via period detection (not turn count), plus raw turn-count backstop
  • CL-5749 / CL-5744 — PostHog LLM cost token property names + broader auth/slash product events
  • CL-5532corbits resume / continue for project-keyed sessions (last / <id> / --pick)
  • CL-5722 — intentional /feedback as headless PostHog survey capture (works when ambient telemetry is off; env kill switches still win)
  • CL-5715 — tighten markdown settle predicate so CI waits for body paint
  • Ship hardening: CliHelpError exits 0, feedback honesty (“queued”), hide /feedback until survey env ids set, drop bare feedback arm on other slash commands

Test plan

  • bun run test — 4443 pass / 0 fail locally
  • CI green
  • Manual: corbits --help prints to stdout, exit 0
  • Manual: corbits resume / resume --pick / resume <id> against a known project
  • Manual: /feedback without survey env → hidden / “not configured”
  • Manual: with CORBITS_FEEDBACK_SURVEY_ID + CORBITS_FEEDBACK_QUESTION_ID set, /feedback hello queues survey response; bare /feedback then Enter cancels; other slash drops arm

Ops note (not a merge blocker)

/feedback needs a PostHog free-text survey created once, then:

CORBITS_FEEDBACK_SURVEY_ID=<uuid>
CORBITS_FEEDBACK_QUESTION_ID=<question uuid>

Without those, the client fails closed and hides the command from the slash menu. No survey id is baked into the binary.

Tickets

CL-5611, CL-5749, CL-5744, CL-5532, CL-5722, CL-5715

…progress signal

A Grok session hard-paused at 10 turns while making real progress through
Linear lookups and code reads, because the pause fired on any tool-only turn
count rather than actual thrash. Forensics over ~/.corbits/projects session
traces (54 sessions with tool-only runs) found healthy streaks topping out at
13 turns and zero sessions repeating an identical tool-call fingerprint 3+
times in a row.

The soft wrap-up nudge now fires at a shared 25-turn threshold for every
model family (still just a check-in, never a stop). The hard pause now
requires the tool calls to actually repeat identically 4 turns in a row
(fingerprintToolCalls, the same helper SubAgentDirector already uses),
independent of overall streak length. Grok drops its miscalibrated 6/10
tool-only pair and shares the default; its shorter sub-agent stall timeout
and finish-bias residual are untouched.
…shared period-detection helper

Lifted the shortest-period-that-repeats-enough search out of
detectRepetition into src/util/period-detection.ts so tool-call
fingerprints can reuse the same detection shape instead of a hand-rolled
consecutive-identical check. stall-watchdog's detectRepetition now
delegates to it; behavior is unchanged, covered by its existing test suite.
The old identicalToolFingerprintStreak only compared each turn to the one
immediately before it, so an alternating A,B tool-call pattern never
triggered the hard pause at any length (critique proved this over 200
turns), while 4 truly identical calls in a row still false-positived on
legitimate polling (rerunning a flaky test, checking a build).

detectToolFingerprintThrash runs exact-period detection over a rolling
fingerprint history instead, catching A,A,A..., A,B,A,B..., and
A,B,C,A,B,C... uniformly. Identical-consecutive (period 1) needs 5 repeats
to tolerate legitimate short polling; any longer cycle needs only 3, since
there's no legitimate reason to repeat a fixed rotation of different tool
calls.

Added scripts/tool-fingerprint-forensics.ts to re-derive these thresholds
against real local session traces: 328 sessions / 559 tool-only runs show
zero repeating cycles of any period 1-8 at all, so both floors sit well
above the measured healthy ceiling. The period-1 floor of 5 is inferred
headroom for the polling case (not measured — the dataset has no repeats to
calibrate against), chosen only to clear the previously false-positived
value of 4.
ChatDirector now keeps a capped rolling history of tool-only-turn
fingerprints and pauses on detectToolFingerprintThrash instead of a
hand-rolled last-fingerprint comparison, so it catches alternating and
rotating tool-call cycles the old check missed entirely, without
false-positiving on a handful of identical polling calls. Removed
toolOnlyNoProgressRepeatLimit from ModelFamilyPolicy — the thrash check is
no longer a single tunable number, and isn't family-specific.

Updated the stale applyToolOnlyLoopProtection JSDoc, which claimed the pause
only fires after the nudge — no longer true, since the thrash check can
(and often does) fire well before the nudge threshold. Updated
docs/ARCHITECTURE.md's director-policy section to describe period detection
accurately, with file:line references.

Tests: alternating A,B for 200 turns now pauses (critique's exact repro), a
3-cycle A,B,C pauses, 4 identical polls followed by varied work does not
pause, a long varied productive streak never pauses, and the nudge path
still does not reply-pause.
Period detection has a hard ceiling (max scanned period 8) and only fires on
an exact repeating tail, so a rotation longer than the ceiling, or a
"phase-broken" cycle that inserts a varying element between repeats (e.g.
A,B,A,B,UNIQUE,...), escapes it forever regardless of streak length.

detectRawToolOnlyBackstop is a secondary, pattern-free check on the raw
tool-only streak length, wired into the ChatDirector so it only fires once
period detection has not already caught the turn. It uses its own pause
message ("ran N tool-only turns without narrating progress") rather than the
pattern-detection wording, since no pattern was found. Threshold is 60,
derived from the current forensic scan (328 sessions with a tool-only run,
559 tool-only runs): run-length p50 3, p90 8, p99 16, max 28 — 60 is more
than double the longest healthy streak ever observed and stays well clear of
the old hard-pause-at-10 that originally motivated this rework.

Also documents on TOOL_FINGERPRINT_MAX_PERIOD that it is a ceiling with no
forensic backing above period 6 (the scan's actual range), and that the
backstop is what catches anything above it.
An earlier commit on this branch claimed the forensic scan
(scripts/tool-fingerprint-forensics.ts) covered periods 1-8 across 328
sessions; the script only ever scanned periods 1-6 (MAX_PERIOD_SCANNED). That
inaccurate claim was repeated in docs/ARCHITECTURE.md and
model-family-policy.ts (stop-policy.ts's copy was fixed in the previous
commit alongside the ceiling comment it lives next to). All three now say
periods 1-6, and model-family-policy.ts's healthy-streak figures are updated
to the run this scan currently produces (p50 3, p90 8, p99 16, max 28) rather
than the older "13-28" summary. Also drops product-name attribution from a
comment that no longer needs it.

docs/ARCHITECTURE.md's director-policy section now also describes the
raw-count backstop added in the previous commit: period detection as the
fast path, the backstop as the final net for cycles above the period
ceiling or phase-broken patterns, with file references for both.
Critique found the round-3 backstop's own escape: narrated text reset both
the period-detection history AND the raw backstop counter, so a model that
narrated one word every ~55 turns kept resetting the backstop before it
could fire. Period detection ("is the model cycling?") still clears on
narration. The backstop is now a separate counter, turnsSinceUserMessage,
that only clears on a genuine fresh user message.

Since narration no longer buys back backstop budget, a legitimately long
autonomous run will now reach it. Reaching the backstop no longer pauses
outright — it nudges for a progress summary. Only if that nudge goes
unanswered for a further full backstop interval, with still no user message
and no thrash detected, does the session hard-pause. A genuine cycle (period
detection) still pauses immediately regardless.

Re-derived the threshold from a fresh local scan of
turns-since-last-genuine-user-message (filtering tool-result echoes, which
are also role "user" in the transcript format): p50 5, p90 14, p99 29, max
32 across 428 runs. Set to 100, roughly 3x the measured max.
… apart from synthetic sends

Round 4 reset the turns-since-user-message backstop on any message.received
event, which synthetic system sends (compaction continuations from
tui/runner.ts, exec/runner.ts, subagent/run.ts) also fire without being
operator input — and compaction fires more often during long tool-only
loops, exactly when the backstop should be counting.

Adds OPERATOR_ORIGINATED_FLAG, set only where a human actually submits a
prompt (TUI prompt-submit and the "send" command result, exec's initial
task). The backstop now resets only when that flag is present, so a future
synthetic sender has to explicitly opt in rather than silently qualifying
by omission.
The commit message, stop-policy.ts, and ARCHITECTURE.md cited a
358-session/428-run scan of turns-since-last-genuine-operator-message with a
stated methodology; no corresponding script or output exists anywhere in
the tree, and the two numbers already disagreed with each other. That
measurement was never taken.

Rewrites all three to state plainly that 100 is a judgment call, not a
measured value, informed only by the streak-length data we do have
(tool-fingerprint-forensics.ts: p50 3, p90 8, p99 16, max 28 across 328
sessions) even though that measures a different quantity than this counter.
Also fixes the stale backstopNudgeFiredAtTurn comment, which claimed a reset
on thrash/escalation that does not happen in code.
Bring the closed PR #401 work onto current main: soft nudge ~25, hard
pause only on fingerprint thrash / delayed backstop, not raw tool-only
count. Grok no longer kills productive multi-step tool work at 10.

# Conflicts:
#	docs/ARCHITECTURE.md
#	src/agent/director.test.ts
#	tests/unit/director.test.ts
Operators can send free-text product feedback with /feedback even when
ambient telemetry is off. Bare /feedback arms the next non-command line;
inline text sends immediately. Env kill switches still block send, and
captureIntentional only accepts survey responses so ambient events cannot
ride the bypass.
Operators can reopen the latest session, a specific UUID session, or
open the interactive picker. Invalid ids and id+--pick combinations
error instead of silently falling through; legacy session trees migrate
on resume by id.
Heading-only frames could pass the prior no-marker check while the bold
body line had not painted yet, causing intermittent CL-5715 flakes.
Ship blockers from the OSS panel: --help exits 0 via CliHelpError,
feedback says queued not sent with truncation notice, hide /feedback
from the slash menu until survey env ids are set, and drop a bare
feedback arm when another slash command runs.
Operators never configure survey routing; same public-id class as the
baked PostHog project key. Env overrides remain for tests and forks.
Classify slash commands and multi-turn /feedback before the session bridge
marks busy or enqueues, so the prompt never shows Working… for a PostHog
survey capture. Flush feedback on capture, keep settings ambient-only, and
sync CLI help docs with the honest wording.
The busy-path fix was dropped at mountRunnerHost, so typecheck failed on
classifySubmit. Empty Enter also never reached exclusive hooks, so armed
/feedback cancel was dead code until the shell lets blank lines through.
@TheGreatAxios
TheGreatAxios merged commit bdfa823 into main Aug 9, 2026
2 checks passed
@TheGreatAxios
TheGreatAxios deleted the wave/non-orch-surface-r1 branch August 9, 2026 20:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant