legion-llm 0.12.14 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +89 -0
  4. data/AGENTS.md +48 -57
  5. data/CHANGELOG.md +455 -0
  6. data/CLAUDE.md +110 -762
  7. data/Gemfile +12 -8
  8. data/README.md +130 -18
  9. data/REFACTOR-HANDOFF.md +299 -0
  10. data/docs/work/planning/p1-results.md +125 -0
  11. data/docs/work/planning/p2-results.md +101 -0
  12. data/docs/work/planning/p3-results.md +133 -0
  13. data/docs/work/planning/p4-results.md +129 -0
  14. data/docs/work/planning/p5-results.md +86 -0
  15. data/legion-llm.gemspec +1 -1
  16. data/lib/legion/llm/api/client_translators/anthropic_messages.rb +764 -0
  17. data/lib/legion/llm/api/client_translators/openai_chat.rb +626 -0
  18. data/lib/legion/llm/api/client_translators/openai_responses.rb +848 -0
  19. data/lib/legion/llm/api/client_translators/shared_extractors.rb +199 -0
  20. data/lib/legion/llm/api/debug_formats.rb +356 -0
  21. data/lib/legion/llm/api/error_translator.rb +71 -0
  22. data/lib/legion/llm/api/inventory_admin.rb +42 -0
  23. data/lib/legion/llm/api/namespaces/anthropic/messages.rb +74 -408
  24. data/lib/legion/llm/api/namespaces/helpers.rb +29 -0
  25. data/lib/legion/llm/api/namespaces/native/routing.rb +4 -23
  26. data/lib/legion/llm/api/namespaces/openai/batches.rb +1 -1
  27. data/lib/legion/llm/api/namespaces/openai/chat/completions.rb +78 -174
  28. data/lib/legion/llm/api/namespaces/openai/responses.rb +94 -456
  29. data/lib/legion/llm/api/native/models.rb +7 -4
  30. data/lib/legion/llm/api/native/providers.rb +16 -14
  31. data/lib/legion/llm/api/native/routing.rb +4 -23
  32. data/lib/legion/llm/api/native/tiers.rb +7 -7
  33. data/lib/legion/llm/api/openai/responses.rb +1 -1
  34. data/lib/legion/llm/api/stream_assembler.rb +788 -0
  35. data/lib/legion/llm/api.rb +10 -4
  36. data/lib/legion/llm/cache/response.rb +2 -2
  37. data/lib/legion/llm/cache.rb +9 -7
  38. data/lib/legion/llm/call/daemon_client.rb +1 -1
  39. data/lib/legion/llm/call/dispatch.rb +347 -215
  40. data/lib/legion/llm/call/embeddings.rb +84 -49
  41. data/lib/legion/llm/call/lex_llm_adapter.rb +89 -23
  42. data/lib/legion/llm/call/providers.rb +0 -18
  43. data/lib/legion/llm/call/registry.rb +2 -2
  44. data/lib/legion/llm/call/structured_output.rb +3 -3
  45. data/lib/legion/llm/compat.rb +41 -5
  46. data/lib/legion/llm/content_hash.rb +52 -0
  47. data/lib/legion/llm/context/compressor.rb +2 -2
  48. data/lib/legion/llm/context/curator.rb +3 -3
  49. data/lib/legion/llm/deprecation.rb +34 -0
  50. data/lib/legion/llm/errors.rb +112 -0
  51. data/lib/legion/llm/fleet/dispatcher.rb +2 -4
  52. data/lib/legion/llm/fleet/lane.rb +16 -1
  53. data/lib/legion/llm/fleet/token_issuer.rb +2 -1
  54. data/lib/legion/llm/helper.rb +10 -10
  55. data/lib/legion/llm/hooks/budget_guard.rb +1 -1
  56. data/lib/legion/llm/hooks/rag_guard.rb +1 -1
  57. data/lib/legion/llm/hooks/reciprocity.rb +2 -2
  58. data/lib/legion/llm/hooks/reflection.rb +2 -2
  59. data/lib/legion/llm/inference/audit_publisher.rb +25 -0
  60. data/lib/legion/llm/inference/context_accounting.rb +131 -0
  61. data/lib/legion/llm/inference/embed_pipeline.rb +187 -0
  62. data/lib/legion/llm/inference/executor/context_window.rb +199 -0
  63. data/lib/legion/llm/inference/executor/escalation.rb +680 -0
  64. data/lib/legion/llm/inference/executor/payload_builder.rb +126 -0
  65. data/lib/legion/llm/inference/executor/routing.rb +487 -0
  66. data/lib/legion/llm/inference/executor/tool_injection.rb +396 -0
  67. data/lib/legion/llm/inference/executor.rb +275 -1663
  68. data/lib/legion/llm/inference/native_tool_loop.rb +212 -53
  69. data/lib/legion/llm/inference/prompt.rb +7 -8
  70. data/lib/legion/llm/inference/request.rb +14 -6
  71. data/lib/legion/llm/inference/route_attempts.rb +41 -36
  72. data/lib/legion/llm/inference/steps/confidence_scoring.rb +1 -1
  73. data/lib/legion/llm/inference/steps/debate.rb +10 -3
  74. data/lib/legion/llm/inference/steps/gaia_advisory.rb +5 -5
  75. data/lib/legion/llm/inference/steps/knowledge_capture.rb +1 -1
  76. data/lib/legion/llm/inference/steps/mcp_discovery.rb +1 -1
  77. data/lib/legion/llm/inference/steps/metering.rb +16 -2
  78. data/lib/legion/llm/inference/steps/post_response.rb +18 -46
  79. data/lib/legion/llm/inference/steps/rag_context.rb +18 -0
  80. data/lib/legion/llm/inference/steps/tier_assigner.rb +4 -4
  81. data/lib/legion/llm/inference/steps/tool_calls.rb +63 -10
  82. data/lib/legion/llm/inference/steps/trigger_match.rb +20 -1
  83. data/lib/legion/llm/inference.rb +143 -31
  84. data/lib/legion/llm/inventory/capabilities.rb +48 -0
  85. data/lib/legion/llm/inventory/discovery/memory_gate.rb +55 -0
  86. data/lib/legion/llm/inventory/discovery/system.rb +138 -0
  87. data/lib/legion/llm/inventory/discovery.rb +565 -0
  88. data/lib/legion/llm/inventory/settings_observer.rb +61 -0
  89. data/lib/legion/llm/inventory/sweeper.rb +56 -0
  90. data/lib/legion/llm/inventory.rb +224 -380
  91. data/lib/legion/llm/metering/tokens.rb +2 -2
  92. data/lib/legion/llm/metering/tracker.rb +1 -1
  93. data/lib/legion/llm/metering.rb +1 -1
  94. data/lib/legion/llm/quality/checker.rb +5 -1
  95. data/lib/legion/llm/quality/confidence/scorer.rb +7 -1
  96. data/lib/legion/llm/router/availability.rb +25 -0
  97. data/lib/legion/llm/router/health_tracker.rb +125 -36
  98. data/lib/legion/llm/router.rb +127 -651
  99. data/lib/legion/llm/scheduling/batch.rb +3 -3
  100. data/lib/legion/llm/scheduling.rb +3 -3
  101. data/lib/legion/llm/settings.rb +100 -25
  102. data/lib/legion/llm/skills/base.rb +1 -1
  103. data/lib/legion/llm/skills/disk_loader.rb +1 -1
  104. data/lib/legion/llm/skills/external_discovery.rb +2 -2
  105. data/lib/legion/llm/tools/confidence.rb +5 -5
  106. data/lib/legion/llm/tools/dispatcher.rb +46 -3
  107. data/lib/legion/llm/tools/special.rb +45 -3
  108. data/lib/legion/llm/transport/message.rb +1 -1
  109. data/lib/legion/llm/types/message.rb +1 -1
  110. data/lib/legion/llm/types/tool_definition.rb +3 -1
  111. data/lib/legion/llm/vector_store/storage.rb +0 -2
  112. data/lib/legion/llm/version.rb +1 -1
  113. data/lib/legion/llm.rb +83 -18
  114. metadata +33 -11
  115. data/lib/legion/llm/discovery/memory_gate.rb +0 -53
  116. data/lib/legion/llm/discovery/rule_generator.rb +0 -216
  117. data/lib/legion/llm/discovery/system.rb +0 -144
  118. data/lib/legion/llm/discovery.rb +0 -521
  119. data/lib/legion/llm/router/arbitrage.rb +0 -138
  120. data/lib/legion/llm/router/escalation/chain.rb +0 -51
  121. data/lib/legion/llm/router/escalation/tracker.rb +0 -76
  122. data/lib/legion/llm/router/rule.rb +0 -133
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7d61b50d6573478325baba59ea7b05a8e7a6bce2c66c453d15eec40b1380b891
4
- data.tar.gz: e14038bcac7c816169e31bc2f8a08fb76331e0bc7b18766f029fe217c3b57d2d
3
+ metadata.gz: 8ab1822ba6aa5df945cd99b3bb2ee5e735080f97a517d4de694725e38f44bf71
4
+ data.tar.gz: 276fd55c3fabce052c0c27a3cd1e84e050b502446a4b047ff8ec88fe829c7f47
5
5
  SHA512:
6
- metadata.gz: e1abe73f183b7b6e135db20bb2bc2b875b6bb40234630c5327a906d38892b6ef83f35c2c9f8a8b0ee193451d1609f1e30c1ebc4b0ebf28e844b3a9e8044ffefa
7
- data.tar.gz: bf44ad26a524c018b042dda702b076c98ef7068aaf48e638305776532d809036778c86a0e4e385974c2d59bffbdf61da4a20067b89d09cbc85a2e5a6b34f5203
6
+ metadata.gz: f1a2ca486fe605683c14a8847ed209a6041c8a557d90b4e6e01676218d3bced83e776ae4352a6f19c09ab19e2f98e129cfb947dcd435a490a6df5c7b95240b31
7
+ data.tar.gz: aa8cbe3c10d72b28d9a31a3e8f3114faaa2e016860e4520532d5872ab01a38e53084184cb22dbb0b7943691819291c17770fb7292da392460200057ba980e638
data/.gitignore CHANGED
@@ -24,3 +24,4 @@ docs/
24
24
  bin/apollo-setup-postreboot.sh
25
25
  bin/apollo-setup-prereboot.sh
26
26
  legionio-bootstrap-uhg-v3.json
27
+ docs/
data/.rubocop.yml CHANGED
@@ -1,8 +1,96 @@
1
+ plugins:
2
+ - rubocop-legion
3
+
4
+ # These rubocop-legion cops surface in the local-path 0.1.7 build but were not
5
+ # in the published 0.1.7 gem the repo previously tracked. They flag broad
6
+ # pre-existing patterns unrelated to the N×N enforcement pass; deferred to
7
+ # their own cleanup task. NoUnderscorePrefixedKwargs / NoInlineSettingDefaults
8
+ # / NoDirectDispatch / NoShapeDuckTyping (the B4 set Phase 6 adopts) remain
9
+ # enabled.
10
+ Legion/RescueLogging/NoCapture:
11
+ Enabled: false
12
+ Legion/ConstantSafety/InheritParam:
13
+ Enabled: false
14
+
15
+ # rubocop-legion 0.1.9 cops — enabled as of P0 but deferred enforcement:
16
+ # these four cops flag broad pre-existing patterns in the codebase that
17
+ # predate the SSOT refactor. They gate NEW code from P1 onward; existing
18
+ # violations are cleaned up within each phase as the code they govern is
19
+ # rewritten. Do not add new violations; do not suppress them with inline
20
+ # rubocop:disable without a tracking comment.
21
+ #
22
+ # TODO(P1): enable Legion/Llm/TaxonomyEnum repo-wide after lane taxonomy
23
+ # is established and all :type/:tier/:circuit_state literals updated.
24
+ # TODO(P1): enable Legion/Llm/RescueLogLevel repo-wide after back-compat
25
+ # :debug rescue handlers are audited and leveled up.
26
+ # TODO(P1): enable Legion/Llm/NoLoopDo repo-wide after cache/drain loops
27
+ # are converted to bounded iteration.
28
+ Legion/Llm/TaxonomyEnum:
29
+ Enabled: false
30
+ Legion/Llm/RescueLogLevel:
31
+ Enabled: false
32
+ Legion/Llm/NoLoopDo:
33
+ Enabled: false
34
+ # SettingsAccessPath is enabled for lib/ only — specs legitimately write
35
+ # settings via the loader path to set up test fixtures.
36
+ Legion/Llm/SettingsAccessPath:
37
+ Exclude:
38
+ - 'spec/**/*'
39
+
1
40
  AllCops:
2
41
  TargetRubyVersion: 3.4
3
42
  NewCops: enable
4
43
  SuggestExtensions: false
5
44
 
45
+ # N×N routing guard cops (Phase 6 enforcement; defaults from rubocop-legion config/default.yml).
46
+ #
47
+ # - NoUnderscorePrefixedKwargs / NoInlineSettingDefaults / NoDirectDispatch are
48
+ # enabled repo-wide.
49
+ # - NoShapeDuckTyping is enabled on the canonical-only surface where the shape
50
+ # contract is fully established by translators. Code at the HTTP/wire ingress
51
+ # (client_translator parse_request, StreamAssembler chunk adapter, DebugFormats
52
+ # request envelope reader, Response.from_provider_message bridge) legitimately
53
+ # inspects shape because that's the layer responsible for normalising into
54
+ # canonical. The cop's scope expands here as the executor's canonical
55
+ # migration (Phase 4 follow-up) lands.
56
+ Legion/Framework/NoShapeDuckTyping:
57
+ Enabled: true
58
+ Include:
59
+ - 'lib/legion/llm/api/**/*.rb'
60
+ - 'lib/legion/llm/inference/**/*.rb'
61
+ Exclude:
62
+ # Legacy tree deprecated this release (R11); deleted next minor.
63
+ - 'lib/legion/llm/api/translators/**/*.rb'
64
+ - 'lib/legion/llm/api/anthropic/**/*.rb'
65
+ - 'lib/legion/llm/api/openai/**/*.rb'
66
+ - 'lib/legion/llm/api/native/**/*.rb'
67
+ - 'lib/legion/llm/api/shared_helpers.rb'
68
+ # Boundary code that legitimately bridges wire ↔ canonical: parse_request
69
+ # at the HTTP ingress, the StreamAssembler chunk adapter (P5 explicitly
70
+ # accepts both Canonical::Chunk and the legacy StreamChunk shape during
71
+ # migration), DebugFormats (reads raw env / reflects request), and
72
+ # shared_extractors (normalises arbitrary thinking content shapes).
73
+ # Pre-canonical inference steps still navigate raw wire hashes; that
74
+ # scope tightens once the executor finishes the canonical migration
75
+ # (Phase 4 follow-up).
76
+ - 'lib/legion/llm/api/client_translators/anthropic_messages.rb'
77
+ - 'lib/legion/llm/api/client_translators/openai_chat.rb'
78
+ - 'lib/legion/llm/api/client_translators/openai_responses.rb'
79
+ - 'lib/legion/llm/api/client_translators/shared_extractors.rb'
80
+ - 'lib/legion/llm/api/stream_assembler.rb'
81
+ - 'lib/legion/llm/api/debug_formats.rb'
82
+ - 'lib/legion/llm/api/namespaces/**/*.rb'
83
+ - 'lib/legion/llm/inference/audit_publisher.rb'
84
+ - 'lib/legion/llm/inference/embed_pipeline.rb'
85
+ - 'lib/legion/llm/inference/enrichment_injector.rb'
86
+ - 'lib/legion/llm/inference/executor.rb'
87
+ - 'lib/legion/llm/inference/executor/**/*.rb'
88
+ - 'lib/legion/llm/inference/native_tool_loop.rb'
89
+ - 'lib/legion/llm/inference/profile.rb'
90
+ - 'lib/legion/llm/inference/response.rb'
91
+ - 'lib/legion/llm/inference/route_attempts.rb'
92
+ - 'lib/legion/llm/inference/steps/**/*.rb'
93
+
6
94
  Layout/LineLength:
7
95
  Max: 195
8
96
  Layout/SpaceAroundEqualsInParameterDefault:
@@ -43,6 +131,7 @@ Naming/PredicateMethod:
43
131
  Enabled: false
44
132
  Metrics/ParameterLists:
45
133
  Max: 9
134
+ CountKeywordArgs: false
46
135
  Style/RedundantConstantBase:
47
136
  Exclude:
48
137
  - 'spec/**/*'
data/AGENTS.md CHANGED
@@ -1,46 +1,61 @@
1
- # legion-llm Agent Notes
1
+ # legion-llm Agent Notes (v0.13.0)
2
2
 
3
- ## Scope
4
-
5
- `legion-llm` provides provider configuration, chat/embed/structured interfaces, dynamic routing, escalation, quality checks, and pipeline execution for Legion.
3
+ `legion-llm` is a **universal translation proxy** for LLM traffic: N client dialects (OpenAI Chat,
4
+ OpenAI Responses, Anthropic Messages) × N provider backends (Bedrock, Anthropic, OpenAI, vLLM,
5
+ Ollama, fleet), any direction. Every request parses once into `Canonical::Request`, is
6
+ routed/executed, then renders once back to the caller's dialect. See `CLAUDE.md` for the full
7
+ invariant set; `README.md` for detailed reference.
6
8
 
7
9
  ## Fast Start
8
10
 
9
11
  ```bash
10
12
  bundle install
11
- bundle exec rspec
12
- bundle exec rubocop
13
+ bundle exec rspec # 0 failures required before commit
14
+ bundle exec rubocop # 0 offenses required
13
15
  ```
14
16
 
17
+ **The in-process matrix harness (`spec/legion/llm/api/matrix/`) is the commit gate.** Touch
18
+ `lib/legion/llm/api/`, the executor, or the canonical/translator boundary → it must pass before push.
19
+
15
20
  ## Primary Entry Points
16
21
 
17
- - `lib/legion/llm.rb`
18
- - `lib/legion/llm/providers.rb`
19
- - `lib/legion/llm/router/`
20
- - `lib/legion/llm/pipeline/`
21
- - `lib/legion/llm/structured_output.rb`
22
- - `lib/legion/llm/embeddings.rb`
23
- - `lib/legion/llm/fleet/`
22
+ - `lib/legion/llm.rb` — facade (`start`, `chat`, `ask`, `embed`, `structured`)
23
+ - `lib/legion/llm/inventory.rb` — **single source of truth** for the model catalog
24
+ - `lib/legion/llm/router.rb` + `router/{candidates,availability,health_tracker,escalation/}` — routing
25
+ - `lib/legion/llm/inference/executor.rb` + `executor/{routing,escalation}.rb` — pipeline
26
+ - `lib/legion/llm/inference/steps/` — the 18 pipeline steps
27
+ - `lib/legion/llm/api/{openai,anthropic,native}/` — client routes
28
+ - `lib/legion/llm/api/client_translators/` — canonical ↔ client wire formats
29
+ - `lib/legion/llm/context/curator.rb` — async conversation curation (context-cost control)
30
+ - Provider behaviour (defaults, capabilities, model filtering) lives in `../extensions-ai/lex-llm-*`
24
31
 
25
32
  ## Guardrails
26
33
 
27
- - Keep typed error behavior and retry semantics stable (`ProviderDown`, `RateLimitError`, `EscalationExhausted`, etc.).
28
- - Routing and escalation must remain deterministic given the same inputs/settings.
29
- - Preserve pipeline feature-flag behavior; avoid forcing pipeline-only code paths.
30
- - Keep provider credentials resolved through settings secret resolution flow; never hardcode secrets.
31
- - Maintain compatibility with direct methods (`chat_direct`, `embed_direct`, `structured_direct`) and daemon-aware flows.
32
- - Health tracker and rule scoring are contract-sensitive; changes require spec updates.
34
+ - **Always translate, never passthrough**; **no `provider == :x` branches** outside translators.
35
+ - **Inventory is the only catalog**; `Discovery`/`Registry`/`HealthTracker` are feeders.
36
+ - Never dispatch a triple absent from the live catalog or unhealthy; **fail over, don't hard-fail**.
37
+ - **Model policy = compliance**: `model_whitelist`/`model_blacklist` honored at dispatch, fail-closed;
38
+ a policy-denied model is terminal (never escalated, never trips circuits).
39
+ - Thinking never crosses providers; mid-stream failover must not kill an in-flight conversation.
40
+ - Every pipeline exit emits ledger events (metering/audit) — no bypasses.
41
+ - `Legion::JSON` only (symbol keys); every `rescue` re-raises or `handle_exception`s; no
42
+ `defined?(Legion::Settings)` guards; `log.*` not `puts`.
43
+ - **No personal/company identifiers in VCS**; never force-push.
44
+ - Routing/escalation deterministic for the same inputs/settings; health-tracker & rule scoring are
45
+ contract-sensitive — changes require spec updates.
33
46
 
34
47
  ## Validation
35
48
 
36
- - Run targeted specs for modified router/pipeline/provider code.
37
- - Before handoff, run full `bundle exec rspec` and `bundle exec rubocop`.
49
+ Run targeted specs for modified router/pipeline/translator code, then full `rspec` + `rubocop` +
50
+ the matrix harness before handoff.
38
51
 
39
52
  ---
40
53
 
41
54
  ## Client Request Headers Reference
42
55
 
43
- Verified from source code (Claude Code binary + Codex `codex-rs` Rust source).
56
+ Verified from source (Claude Code binary + Codex `codex-rs`). Useful when working on `/v1/messages`
57
+ and `/v1/responses` handlers. Routing/identity headers `X-Legion-{Provider,Model,Instance,Tier}` are
58
+ honored as **rules** (hard constraints), not hints.
44
59
 
45
60
  ### Claude Code → `POST /v1/messages`
46
61
 
@@ -48,15 +63,10 @@ Verified from source code (Claude Code binary + Codex `codex-rs` Rust source).
48
63
  |---|---|---|
49
64
  | `X-Claude-Code-Session-Id` | Stable UUID for the CLI session | Yes |
50
65
  | `x-app` | `"cli"` (foreground) or `"cli-bg"` (background) | Yes |
51
- | `x-claude-remote-session-id` | Remote container session ID | Conditional |
52
- | `x-claude-remote-container-id` | Remote container ID | Conditional |
53
- | `x-claude-code-agent-id` | Agent UUID for multi-agent sessions | Conditional |
54
- | `x-claude-code-parent-agent-id` | Parent agent UUID (spawned subagent) | Conditional |
55
- | `x-client-app` | Additional client app identifier | Conditional |
56
-
57
- Conversation threading is **stateless** — full `messages[]` history sent in the body on every request. No conversation ID, turn ID, or `x-client-request-id` header is sent.
66
+ | `x-claude-code-agent-id` / `x-claude-code-parent-agent-id` | Agent / parent-agent UUIDs | Conditional |
58
67
 
59
- In Rack/Sinatra env keys, headers arrive as `HTTP_X_CLAUDE_CODE_SESSION_ID`, `HTTP_X_APP`, etc.
68
+ Threading is **stateless** full `messages[]` history in the body every request; no conversation/turn
69
+ ID header. In Rack env: `HTTP_X_CLAUDE_CODE_SESSION_ID`, `HTTP_X_APP`, etc.
60
70
 
61
71
  ### Codex → `POST /v1/responses`
62
72
 
@@ -66,34 +76,15 @@ In Rack/Sinatra env keys, headers arrive as `HTTP_X_CLAUDE_CODE_SESSION_ID`, `HT
66
76
  | `thread-id` | Stable UUID for the thread/conversation | Yes |
67
77
  | `x-client-request-id` | Same value as `thread-id` | Yes |
68
78
  | `x-codex-installation-id` | Installation-scoped UUID | Yes |
69
- | `x-codex-window-id` | `"{thread_id}:{window_generation}"` | Yes |
70
- | `x-codex-turn-state` | Sticky-routing token returned by server, replayed by client | After first response |
71
- | `x-codex-turn-metadata` | Per-turn observability metadata | Conditional |
72
- | `x-codex-parent-thread-id` | Parent thread UUID (sub-agents) | Conditional |
73
- | `x-openai-subagent` | Sub-agent type (`"review"`, `"compact"`, `"memory_consolidation"`, etc.) | Conditional |
74
- | `x-openai-memgen-request` | `"true"` for memory generation requests | Conditional |
75
-
76
- In Rack/Sinatra env keys: `HTTP_SESSION_ID`, `HTTP_THREAD_ID`, `HTTP_X_CLIENT_REQUEST_ID`, `HTTP_X_CODEX_INSTALLATION_ID`, etc.
77
-
78
- **`HTTP_THREAD_ID` is the stable Codex thread/conversation ID** — it is stable for the lifetime of a thread, not per-request. `HTTP_X_CLIENT_REQUEST_ID` equals `HTTP_THREAD_ID` (Codex sets them to the same value).
79
-
80
- Conversation threading over HTTP uses full input in body (stateless like Anthropic). Over WebSocket, `previous_response_id` is sent in the request body to enable delta-only input.
79
+ | `x-codex-turn-state` | Sticky-routing token, replayed by client | After first response |
80
+ | `x-openai-subagent` | Sub-agent type (`review`, `compact`, …) | Conditional |
81
81
 
82
- ### Practical Usage in `/v1/messages` and `/v1/responses` Handlers
82
+ `HTTP_THREAD_ID` is the stable thread/conversation ID (not per-request); `HTTP_X_CLIENT_REQUEST_ID`
83
+ equals it. HTTP threading is stateless (full input in body); over WebSocket, `previous_response_id`
84
+ enables delta-only input.
83
85
 
84
86
  ```ruby
85
- # Stable request ID (Claude Code sends X-Claude-Code-Session-Id; Codex sends x-client-request-id = thread-id)
86
- request_id = env['HTTP_X_CLIENT_REQUEST_ID'] || "req_#{SecureRandom.hex(12)}"
87
-
88
- # Stable conversation/thread ID
89
- # Claude Code: no header — generate per-request or use Legion conversation tracking
90
- # Codex: HTTP_THREAD_ID is stable for the thread lifetime
91
- conversation_id = env['HTTP_THREAD_ID'] ||
92
- env['HTTP_X_LEGION_CONVERSATION_ID'] ||
93
- body[:conversation_id] ||
94
- "conv_#{SecureRandom.hex(8)}"
95
-
96
- # Identify the calling client
97
- claude_code_session = env['HTTP_X_CLAUDE_CODE_SESSION_ID'] # present only for Claude Code
98
- codex_installation = env['HTTP_X_CODEX_INSTALLATION_ID'] # present only for Codex
87
+ request_id = env['HTTP_X_CLIENT_REQUEST_ID'] || "req_#{SecureRandom.hex(12)}"
88
+ conversation_id = env['HTTP_THREAD_ID'] || env['HTTP_X_LEGION_CONVERSATION_ID'] ||
89
+ body[:conversation_id] || "conv_#{SecureRandom.hex(8)}"
99
90
  ```