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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +89 -0
- data/AGENTS.md +48 -57
- data/CHANGELOG.md +455 -0
- data/CLAUDE.md +110 -762
- data/Gemfile +12 -8
- data/README.md +130 -18
- data/REFACTOR-HANDOFF.md +299 -0
- data/docs/work/planning/p1-results.md +125 -0
- data/docs/work/planning/p2-results.md +101 -0
- data/docs/work/planning/p3-results.md +133 -0
- data/docs/work/planning/p4-results.md +129 -0
- data/docs/work/planning/p5-results.md +86 -0
- data/legion-llm.gemspec +1 -1
- data/lib/legion/llm/api/client_translators/anthropic_messages.rb +764 -0
- data/lib/legion/llm/api/client_translators/openai_chat.rb +626 -0
- data/lib/legion/llm/api/client_translators/openai_responses.rb +848 -0
- data/lib/legion/llm/api/client_translators/shared_extractors.rb +199 -0
- data/lib/legion/llm/api/debug_formats.rb +356 -0
- data/lib/legion/llm/api/error_translator.rb +71 -0
- data/lib/legion/llm/api/inventory_admin.rb +42 -0
- data/lib/legion/llm/api/namespaces/anthropic/messages.rb +74 -408
- data/lib/legion/llm/api/namespaces/helpers.rb +29 -0
- data/lib/legion/llm/api/namespaces/native/routing.rb +4 -23
- data/lib/legion/llm/api/namespaces/openai/batches.rb +1 -1
- data/lib/legion/llm/api/namespaces/openai/chat/completions.rb +78 -174
- data/lib/legion/llm/api/namespaces/openai/responses.rb +94 -456
- data/lib/legion/llm/api/native/models.rb +7 -4
- data/lib/legion/llm/api/native/providers.rb +16 -14
- data/lib/legion/llm/api/native/routing.rb +4 -23
- data/lib/legion/llm/api/native/tiers.rb +7 -7
- data/lib/legion/llm/api/openai/responses.rb +1 -1
- data/lib/legion/llm/api/stream_assembler.rb +788 -0
- data/lib/legion/llm/api.rb +10 -4
- data/lib/legion/llm/cache/response.rb +2 -2
- data/lib/legion/llm/cache.rb +9 -7
- data/lib/legion/llm/call/daemon_client.rb +1 -1
- data/lib/legion/llm/call/dispatch.rb +347 -215
- data/lib/legion/llm/call/embeddings.rb +84 -49
- data/lib/legion/llm/call/lex_llm_adapter.rb +89 -23
- data/lib/legion/llm/call/providers.rb +0 -18
- data/lib/legion/llm/call/registry.rb +2 -2
- data/lib/legion/llm/call/structured_output.rb +3 -3
- data/lib/legion/llm/compat.rb +41 -5
- data/lib/legion/llm/content_hash.rb +52 -0
- data/lib/legion/llm/context/compressor.rb +2 -2
- data/lib/legion/llm/context/curator.rb +3 -3
- data/lib/legion/llm/deprecation.rb +34 -0
- data/lib/legion/llm/errors.rb +112 -0
- data/lib/legion/llm/fleet/dispatcher.rb +2 -4
- data/lib/legion/llm/fleet/lane.rb +16 -1
- data/lib/legion/llm/fleet/token_issuer.rb +2 -1
- data/lib/legion/llm/helper.rb +10 -10
- data/lib/legion/llm/hooks/budget_guard.rb +1 -1
- data/lib/legion/llm/hooks/rag_guard.rb +1 -1
- data/lib/legion/llm/hooks/reciprocity.rb +2 -2
- data/lib/legion/llm/hooks/reflection.rb +2 -2
- data/lib/legion/llm/inference/audit_publisher.rb +25 -0
- data/lib/legion/llm/inference/context_accounting.rb +131 -0
- data/lib/legion/llm/inference/embed_pipeline.rb +187 -0
- data/lib/legion/llm/inference/executor/context_window.rb +199 -0
- data/lib/legion/llm/inference/executor/escalation.rb +680 -0
- data/lib/legion/llm/inference/executor/payload_builder.rb +126 -0
- data/lib/legion/llm/inference/executor/routing.rb +487 -0
- data/lib/legion/llm/inference/executor/tool_injection.rb +396 -0
- data/lib/legion/llm/inference/executor.rb +275 -1663
- data/lib/legion/llm/inference/native_tool_loop.rb +212 -53
- data/lib/legion/llm/inference/prompt.rb +7 -8
- data/lib/legion/llm/inference/request.rb +14 -6
- data/lib/legion/llm/inference/route_attempts.rb +41 -36
- data/lib/legion/llm/inference/steps/confidence_scoring.rb +1 -1
- data/lib/legion/llm/inference/steps/debate.rb +10 -3
- data/lib/legion/llm/inference/steps/gaia_advisory.rb +5 -5
- data/lib/legion/llm/inference/steps/knowledge_capture.rb +1 -1
- data/lib/legion/llm/inference/steps/mcp_discovery.rb +1 -1
- data/lib/legion/llm/inference/steps/metering.rb +16 -2
- data/lib/legion/llm/inference/steps/post_response.rb +18 -46
- data/lib/legion/llm/inference/steps/rag_context.rb +18 -0
- data/lib/legion/llm/inference/steps/tier_assigner.rb +4 -4
- data/lib/legion/llm/inference/steps/tool_calls.rb +63 -10
- data/lib/legion/llm/inference/steps/trigger_match.rb +20 -1
- data/lib/legion/llm/inference.rb +143 -31
- data/lib/legion/llm/inventory/capabilities.rb +48 -0
- data/lib/legion/llm/inventory/discovery/memory_gate.rb +55 -0
- data/lib/legion/llm/inventory/discovery/system.rb +138 -0
- data/lib/legion/llm/inventory/discovery.rb +565 -0
- data/lib/legion/llm/inventory/settings_observer.rb +61 -0
- data/lib/legion/llm/inventory/sweeper.rb +56 -0
- data/lib/legion/llm/inventory.rb +224 -380
- data/lib/legion/llm/metering/tokens.rb +2 -2
- data/lib/legion/llm/metering/tracker.rb +1 -1
- data/lib/legion/llm/metering.rb +1 -1
- data/lib/legion/llm/quality/checker.rb +5 -1
- data/lib/legion/llm/quality/confidence/scorer.rb +7 -1
- data/lib/legion/llm/router/availability.rb +25 -0
- data/lib/legion/llm/router/health_tracker.rb +125 -36
- data/lib/legion/llm/router.rb +127 -651
- data/lib/legion/llm/scheduling/batch.rb +3 -3
- data/lib/legion/llm/scheduling.rb +3 -3
- data/lib/legion/llm/settings.rb +100 -25
- data/lib/legion/llm/skills/base.rb +1 -1
- data/lib/legion/llm/skills/disk_loader.rb +1 -1
- data/lib/legion/llm/skills/external_discovery.rb +2 -2
- data/lib/legion/llm/tools/confidence.rb +5 -5
- data/lib/legion/llm/tools/dispatcher.rb +46 -3
- data/lib/legion/llm/tools/special.rb +45 -3
- data/lib/legion/llm/transport/message.rb +1 -1
- data/lib/legion/llm/types/message.rb +1 -1
- data/lib/legion/llm/types/tool_definition.rb +3 -1
- data/lib/legion/llm/vector_store/storage.rb +0 -2
- data/lib/legion/llm/version.rb +1 -1
- data/lib/legion/llm.rb +83 -18
- metadata +33 -11
- data/lib/legion/llm/discovery/memory_gate.rb +0 -53
- data/lib/legion/llm/discovery/rule_generator.rb +0 -216
- data/lib/legion/llm/discovery/system.rb +0 -144
- data/lib/legion/llm/discovery.rb +0 -521
- data/lib/legion/llm/router/arbitrage.rb +0 -138
- data/lib/legion/llm/router/escalation/chain.rb +0 -51
- data/lib/legion/llm/router/escalation/tracker.rb +0 -76
- data/lib/legion/llm/router/rule.rb +0 -133
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8ab1822ba6aa5df945cd99b3bb2ee5e735080f97a517d4de694725e38f44bf71
|
|
4
|
+
data.tar.gz: 276fd55c3fabce052c0c27a3cd1e84e050b502446a4b047ff8ec88fe829c7f47
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f1a2ca486fe605683c14a8847ed209a6041c8a557d90b4e6e01676218d3bced83e776ae4352a6f19c09ab19e2f98e129cfb947dcd435a490a6df5c7b95240b31
|
|
7
|
+
data.tar.gz: aa8cbe3c10d72b28d9a31a3e8f3114faaa2e016860e4520532d5872ab01a38e53084184cb22dbb0b7943691819291c17770fb7292da392460200057ba980e638
|
data/.gitignore
CHANGED
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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/
|
|
19
|
-
- `lib/legion/llm/router
|
|
20
|
-
- `lib/legion/llm/pipeline
|
|
21
|
-
- `lib/legion/llm/
|
|
22
|
-
- `lib/legion/llm/
|
|
23
|
-
- `lib/legion/llm/
|
|
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
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
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
|
-
|
|
37
|
-
|
|
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
|
|
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-
|
|
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
|
-
|
|
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-
|
|
70
|
-
| `x-
|
|
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
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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
|
```
|