Skip to content

Headless Claude worker: provider-neutral events, systemd unit, explicit executor - #13670

Merged
briansrls merged 6 commits into
mainfrom
session/jolly-pike-775-headless-claude
Oct 10, 2026
Merged

briansrls merged 6 commits into
mainfrom
session/jolly-pike-775-headless-claude

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Re-lands the headless Claude worker from gunbc#13662 onto current main (outside the v1 closeout): shape_claude_print_argv, systemd unit (stdout → events, stderr → log), ExecutorDefault fail-closed (Claude only via ?executor=claude), no Anthropic API-key arm, tmux event-pipe emission only on tmux containers.
  • Replacement migration at the root (DESIGN §3 delete-first): deleted CodexProviderEventKind, CodexProviderEvent, CodexEventParse / CodexEventsParsed / CodexEventsRefused, CodexParseFold, claude_code_line_codex_kind. Replaced by ProviderEventKind, ProviderEvent, ProviderEventParse (ProviderEventsParsed | ProviderEventStreamRefused), ProviderParseFold, and one classifier classify_provider_event_line. jq bounds and preserves fields and makes no skip/refuse verdict.
  • Declared Claude line outcomes: skip rate_limit_event and user-without-tool_result; refuse unknown types and malformed Claude lines. Supplied-value claims for those arms; inhabitance a_claude_stream_json_turn_is_read_as_provider_events_including_skipped_rate_limit (40/40 claims in roadmap_provider_events_witness_test PASS under local claim_batch). Headless spawn claims PASS. Two pre-existing alignment-preamble claims in the dispatch witness (2-scm-git-upstream-model) FAIL on current main because that node is no longer unrooted; they are unchanged by this PR.

Operator custody order (do not run from this PR)

The order is the operator's and is fixed: safe WIF lands (gunbc#13660, recut to reviewed-main-only privileged federations) -> your PR is approved and merged -> the exact merged revision is deployed -> the operator runs the custody converge (gh workflow run fleet-converge.yml --ref main -f host=srv1 -f mode=host_credential_custody_converge -f credential=claude_code_oauth_harness_token) -> one explicit POST /dispatch/{node}?executor=claude -> observe the attempt unit gunbc-attempt--.service and /opt/gunbc/attempt-state/-a/provider-events.jsonl.

This PR does not perform or request the custody converge, does not place /etc/gunbc-claude-code/oauth-token-harness, and does not restart live services.

Test plan

  • claim_batch on dag/test/claim/roadmap/roadmap_provider_events_witness_test.dag (40 PASS, including classifier skip/refuse arms and inhabitance)
  • dag/test/claim/extdeps_llm_claude_trust_witness_test.dag (12 PASS)
  • dag/test/claim/claude_code_dispatch_witness_test.dag (resolves; claims run in the same batch as dispatch actuator)
  • Headless Claude spawn claims PASS (witness_headless_claude_uses_a_transient_unit_with_stream_json_on_the_events_path, witness_headless_claude_spawn_does_not_refuse_as_tmux_event_pipe)
  • CI required gate on this PR

Made with Cursor

@gunbai-bot
gunbai-bot Bot marked this pull request as draft October 10, 2026 04:11
@gunbai-bot

gunbai-bot Bot commented Oct 10, 2026

Copy link
Copy Markdown
Contributor Author

review 78407: both findings were real on this head and are fixed.

  • jq result no longer writes is_error: true or subtype: "unknown" when the field is missing or the wrong JSON type. It omits those members so claude_result_line refuses (DESIGN §5: refuse, never widen). Assistant/user content-block type is passed through, not rewritten to "unknown".
  • claude_message_content_has_type is no longer a Bool that collapses malformed content to "no tool use". A duplicated message/content, a non-array content, or a malformed/null block type is ClaudeCodeLineUnreadable, which the classifier refuses.

Claims: classify_result_with_non_boolean_is_error_is_refused, classify_malformed_assistant_block_type_is_refused, classify_non_array_assistant_content_is_refused, jq_result_arm_does_not_fabricate_is_error_or_subtype. 44/44 roadmap_provider_events claims PASS; 25/25 claude_code_dispatch claims PASS.

— sent from jolly-pike-775

@gunbai-bot gunbai-bot Bot mentioned this pull request Oct 10, 2026
gunbc-ci-auto-heal and others added 6 commits October 10, 2026 15:33
…ction.

When the harness has no spark, ExecutorDefault can admit Claude if custody is present; events stay in the belt's Codex envelope. Credential converge on srv1 remains an operator decision.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ansmit effort.

parse_codex_jsonl now classifies bounded Claude stream-json via claude_code_line_codex_kind; jq only bounds those lines. ExecutorDefault stays a harness refusal. Print argv carries --effort.

Co-authored-by: Cursor <cursoragent@cursor.com>
Claude and harness systemd spawn no longer derive readiness from tee/pipe emission or refuse as tmux-event-pipe-emit.

Co-authored-by: Cursor <cursoragent@cursor.com>
Delete the Codex-named event vocabulary at the root. One typed classifier
decides observe, skip, or refuse; jq only bounds and preserves fields.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ent.

jq no longer fills is_error or subtype; the typed reader refuses a missing
or non-boolean is_error. Assistant and user content that is not a well-formed
block list is unreadable rather than treated as no tool use.

Co-authored-by: Cursor <cursoragent@cursor.com>
…perties.

Rebase onto #13641 left stream_properties using exit_hook without declaring it, so the headless spawn claims could not resolve.

Co-authored-by: Cursor <cursoragent@cursor.com>
@gunbai-bot
gunbai-bot Bot force-pushed the session/jolly-pike-775-headless-claude branch from 7995f44 to 142a023 Compare October 10, 2026 16:00
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review October 10, 2026 16:04
@gunbai-bot

gunbai-bot Bot commented Oct 10, 2026

Copy link
Copy Markdown
Contributor Author

Review 78437 (claude, REQUEST_CHANGES on 142a023): all three findings are accepted and a dedicated lane of smart-gull-336 is fixing them on this branch (the lane that wrote the PR has closed): (1) the Claude bound moves out of the jq string into the typed reader extdeps.llm.claude_code_stream_json claude_code_stream_line and the .dag classifier, and the jq scaffold shrinks back to its admitted Codex-only shape; (2) the provider-neutral rename is finished at its root, CodexItemActivity and its arms replaced by provider-neutral names with every consumer following in one change and no alias kept; (3) the scaffold rationale leaves the prose String row for an annotation or a typed carrier. What the review called sound stays as is. The PR is not to be landed before that commit.

— sent from smart-gull-336

@briansrls
briansrls merged commit 5287a8f into main Oct 10, 2026
1 check passed
@briansrls
briansrls deleted the session/jolly-pike-775-headless-claude branch October 10, 2026 16:27
@gunbai-bot

gunbai-bot Bot commented Oct 10, 2026

Copy link
Copy Markdown
Contributor Author

Addressing review 78437 item by item at a25369e:

  1. Claude bound is no longer in the jq string. codex_provider_event_projection_filter is restored to its Codex-only admitted shape (item/turn/error/round.usage/placement envelopes). Which Claude lines count, which content blocks classify as tool_use / tool_result, skip vs observe vs refuse, and fail-closed malformed content all go through claude_code_stream_line then classify_claude_code_stream_line. A witness jq_filter_carries_no_claude_stream_semantics reddens if those Claude branches reappear in the filter. Four supplied-value claims over constructed ClaudeCodeStreamLine values redden if the classifier maps tool use / reporting / skip / result-error onto the wrong arms.

  2. Provider-neutral rename at the root (no alias). CodexItemActivity and its Codex-prefixed arms are deleted in the same change. Mapping:

    • CodexItemActivity → ProviderItemActivity
    • CodexRunningCommand → RunningCommand
    • CodexEditingFiles → EditingFiles
    • CodexUsingConnectedTool → UsingConnectedTool
    • CodexSearching → Searching
    • CodexUpdatingPlan → UpdatingPlan
    • CodexReasoningItem → ReasoningItem
    • CodexReporting → Reporting
    • CodexUnclassifiedItem → UnclassifiedItem

    Harness arms (HarnessReadingFile etc.) are unchanged. Consumers (classify_claude_code_stream_line, item labels, fold, witnesses, presentation comments) follow in this commit. git grep of the old activity-arm names returns only history.

  3. Scaffold rationale is not a String row. Deleted data codex_projection_filter_scaffold_note: String. The dissolution note is a leading // annotation on codex_provider_event_projection_filter. std.disposition Scaffold exists; an unconsumed extra row would be dangling, so the annotation is the carrier.

Evidence: local seed, then gunbc run --claim-run on dag/test/claim/roadmap/roadmap_provider_events_witness_test.dag 48/48 PASS; claim_batch on the two headless Claude spawn witnesses 2/2 PASS.

Not marking ready; operator lands.

@gunbai-bot

gunbai-bot Bot commented Oct 10, 2026

Copy link
Copy Markdown
Contributor Author

#13670 merged at 5287a8f without review 78437. The three items are on follow-up #13682 at ad7045e (same working branch, merged with current main).

  1. jq restored to Codex-only; Claude bound is claude_code_stream_line + classify_claude_code_stream_line.
  2. CodexItemActivity and Codex-prefixed arms renamed at the root (no alias) to ProviderItemActivity / RunningCommand / EditingFiles / UsingConnectedTool / Searching / UpdatingPlan / ReasoningItem / Reporting / UnclassifiedItem.
  3. codex_projection_filter_scaffold_note String row deleted; rationale is a leading // on the filter.

Operator lands #13682; this session is not merging.

gunbai-bot Bot pushed a commit that referenced this pull request Oct 10, 2026
…and repoint the plan doc at the annotation (review 78440)

Review 78439: the belt reads Claude's attempt JSONL through codex_provider_event_projection_filter (roadmap_belt_actuate -> dispatch_events_projection_argv_at), so removing the result / system / assistant / user arms starved classify_claude_code_stream_line of every field but type -- tool_use lines read as Reporting, tool_result lines were skipped, result lines were unreadable. The filter's Claude arms are restored exactly as #13670 landed them (bounded by the CharacterCount budgets; no classification in jq), and the annotation now says what the filter is: a bound before the substrate reads a provider line, Codex or Claude, whose semantic decisions live in the typed readers and classify_*; the arms are the declared debt review 78437 named and retire with the filter under the substrate-side line read or a provider-neutral bound. The witness that asserted the jq text carried no Claude arms is replaced by one that asserts the arms are present and that no provider-neutral activity name appears in the jq text. The provider-neutral rename and the annotation move stand. Review 78440: docs/plans/shell-to-dag-residual-census-and-arc-completion.md cited the deleted codex_projection_filter_scaffold_note row and called the budgets Int rows; it now cites the annotation on codex_provider_event_projection_filter and the CharacterCount budgets.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gunbai-bot Bot added a commit that referenced this pull request Oct 10, 2026
… Claude off the jq scaffold (review 78437 on #13670) (#13683)

* Finish the provider-neutral item vocabulary and keep Claude off the jq scaffold.

Classify Claude stream lines through the typed reader; restore the Codex-only jq filter; rename CodexItemActivity and its Codex-prefixed arms at the root; move the scaffold rationale onto a leading annotation.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit a25369e)

* Keep the bounded Claude pass-through in the jq filter (review 78439) and repoint the plan doc at the annotation (review 78440)

Review 78439: the belt reads Claude's attempt JSONL through codex_provider_event_projection_filter (roadmap_belt_actuate -> dispatch_events_projection_argv_at), so removing the result / system / assistant / user arms starved classify_claude_code_stream_line of every field but type -- tool_use lines read as Reporting, tool_result lines were skipped, result lines were unreadable. The filter's Claude arms are restored exactly as #13670 landed them (bounded by the CharacterCount budgets; no classification in jq), and the annotation now says what the filter is: a bound before the substrate reads a provider line, Codex or Claude, whose semantic decisions live in the typed readers and classify_*; the arms are the declared debt review 78437 named and retire with the filter under the substrate-side line read or a provider-neutral bound. The witness that asserted the jq text carried no Claude arms is replaced by one that asserts the arms are present and that no provider-neutral activity name appears in the jq text. The provider-neutral rename and the annotation move stand. Review 78440: docs/plans/shell-to-dag-residual-census-and-arc-completion.md cited the deleted codex_projection_filter_scaffold_note row and called the budgets Int rows; it now cites the annotation on codex_provider_event_projection_filter and the CharacterCount budgets.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Restore jq_result_arm_does_not_fabricate_is_error_or_subtype beside the bound (review 78441)

The guard that the jq result arm passes is_error through only when it is a boolean and never defaults it was deleted with the Claude arms and not restored with them. DESIGN section 4b(4): the discriminating red stays enrolled.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
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